diff --git a/plexpy/__init__.py b/plexpy/__init__.py
index ec243428..a2c473e6 100644
--- a/plexpy/__init__.py
+++ b/plexpy/__init__.py
@@ -36,12 +36,14 @@ import activity_handler
import activity_pinger
import config
import database
+import libraries
import logger
import mobile_app
import notification_handler
import notifiers
import plextv
import pmsconnect
+import users
import versioncheck
import plexpy.config
@@ -213,16 +215,15 @@ def initialize(config_file):
# 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()
- pmsconnect.get_server_friendly_name()
+ plextv.get_server_resources()
# Refresh the users list on startup
if CONFIG.PMS_TOKEN and CONFIG.REFRESH_USERS_ON_STARTUP:
- plextv.refresh_users()
+ users.refresh_users()
# Refresh the libraries list on startup
if CONFIG.PMS_IP and CONFIG.PMS_TOKEN and CONFIG.REFRESH_LIBRARIES_ON_STARTUP:
- pmsconnect.refresh_libraries()
+ libraries.refresh_libraries()
# Store the original umask
UMASK = os.umask(0)
@@ -323,14 +324,8 @@ def initialize_scheduler():
hours=backup_hours, minutes=0, seconds=0, args=(True, True))
if WS_CONNECTED and CONFIG.PMS_IP and CONFIG.PMS_TOKEN:
- #schedule_job(activity_pinger.check_active_sessions, 'Check for active sessions',
- # hours=0, minutes=0, seconds=1)
- #schedule_job(activity_pinger.check_recently_added, 'Check for recently added items',
- # hours=0, minutes=0, seconds=monitor_seconds * bool(CONFIG.NOTIFY_RECENTLY_ADDED))
- schedule_job(plextv.get_real_pms_url, 'Refresh Plex server URLs',
+ schedule_job(plextv.get_server_resources, 'Refresh Plex server URLs',
hours=12 * (not bool(CONFIG.PMS_URL_MANUAL)), minutes=0, seconds=0)
- schedule_job(pmsconnect.get_server_friendly_name, 'Refresh Plex server name',
- hours=12, minutes=0, seconds=0)
schedule_job(activity_pinger.check_server_access, 'Check for Plex remote access',
hours=0, minutes=0, seconds=60 * bool(CONFIG.MONITOR_REMOTE_ACCESS))
@@ -341,9 +336,9 @@ def initialize_scheduler():
user_hours = CONFIG.REFRESH_USERS_INTERVAL if 1 <= CONFIG.REFRESH_USERS_INTERVAL <= 24 else 12
library_hours = CONFIG.REFRESH_LIBRARIES_INTERVAL if 1 <= CONFIG.REFRESH_LIBRARIES_INTERVAL <= 24 else 12
- schedule_job(plextv.refresh_users, 'Refresh users list',
+ schedule_job(users.refresh_users, 'Refresh users list',
hours=user_hours, minutes=0, seconds=0)
- schedule_job(pmsconnect.refresh_libraries, 'Refresh libraries list',
+ schedule_job(libraries.refresh_libraries, 'Refresh libraries list',
hours=library_hours, minutes=0, seconds=0)
schedule_job(activity_pinger.check_server_response, 'Check server response',
@@ -351,9 +346,7 @@ def initialize_scheduler():
else:
# Cancel all jobs
- schedule_job(plextv.get_real_pms_url, 'Refresh Plex server URLs',
- hours=0, minutes=0, seconds=0)
- schedule_job(pmsconnect.get_server_friendly_name, 'Refresh Plex server name',
+ schedule_job(plextv.get_server_resources, 'Refresh Plex server URLs',
hours=0, minutes=0, seconds=0)
schedule_job(activity_pinger.check_server_access, 'Check for Plex remote access',
@@ -361,9 +354,9 @@ def initialize_scheduler():
schedule_job(activity_pinger.check_server_updates, 'Check for Plex updates',
hours=0, minutes=0, seconds=0)
- schedule_job(plextv.refresh_users, 'Refresh users list',
+ schedule_job(users.refresh_users, 'Refresh users list',
hours=0, minutes=0, seconds=0)
- schedule_job(pmsconnect.refresh_libraries, 'Refresh libraries list',
+ schedule_job(libraries.refresh_libraries, 'Refresh libraries list',
hours=0, minutes=0, seconds=0)
# Schedule job to reconnect websocket
diff --git a/plexpy/activity_pinger.py b/plexpy/activity_pinger.py
index 373b8b80..ae7341bf 100644
--- a/plexpy/activity_pinger.py
+++ b/plexpy/activity_pinger.py
@@ -278,7 +278,6 @@ def check_server_response():
def check_server_access():
-
with monitor_lock:
pms_connect = pmsconnect.PmsConnect()
server_response = pms_connect.get_server_response()
@@ -287,7 +286,7 @@ def check_server_access():
# Check for remote access
if server_response:
-
+
mapping_state = server_response['mapping_state']
mapping_error = server_response['mapping_error']
diff --git a/plexpy/api2.py b/plexpy/api2.py
index 5355c904..4706daa7 100644
--- a/plexpy/api2.py
+++ b/plexpy/api2.py
@@ -32,10 +32,10 @@ import xmltodict
import plexpy
import config
import database
+import libraries
import logger
import mobile_app
-import plextv
-import pmsconnect
+import users
class API2:
@@ -345,14 +345,14 @@ class API2:
def refresh_libraries_list(self, **kwargs):
""" Refresh the Tautulli libraries list."""
- data = pmsconnect.refresh_libraries()
+ data = libraries.refresh_libraries()
self._api_result_type = 'success' if data else 'error'
return data
def refresh_users_list(self, **kwargs):
""" Refresh the Tautulli users list."""
- data = plextv.refresh_users()
+ data = users.refresh_users()
self._api_result_type = 'success' if data else 'error'
return data
diff --git a/plexpy/common.py b/plexpy/common.py
index f88c217d..d3618a24 100644
--- a/plexpy/common.py
+++ b/plexpy/common.py
@@ -140,10 +140,10 @@ SCHEDULER_LIST = ['Check GitHub for updates',
'Check for recently added items',
'Check for Plex updates',
'Check for Plex remote access',
+ 'Check server response',
'Refresh users list',
'Refresh libraries list',
'Refresh Plex server URLs',
- 'Refresh Plex server name',
'Backup Tautulli database',
'Backup Tautulli config'
]
diff --git a/plexpy/config.py b/plexpy/config.py
index 16f64796..10308c22 100644
--- a/plexpy/config.py
+++ b/plexpy/config.py
@@ -45,6 +45,7 @@ _CONFIG_DEFINITIONS = {
'PLEXWATCH_DATABASE': (str, 'PlexWatch', ''),
'PMS_IDENTIFIER': (str, 'PMS', ''),
'PMS_IP': (str, 'PMS', '127.0.0.1'),
+ 'PMS_IS_CLOUD': (int, 'PMS', 0),
'PMS_IS_REMOTE': (int, 'PMS', 0),
'PMS_LOGS_FOLDER': (str, 'PMS', ''),
'PMS_LOGS_LINE_CAP': (int, 'PMS', 1000),
diff --git a/plexpy/http_handler.py b/plexpy/http_handler.py
index 223a65e1..966adb54 100644
--- a/plexpy/http_handler.py
+++ b/plexpy/http_handler.py
@@ -1,24 +1,28 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-# This file is part of Tautulli.
+# This file is part of PlexPy.
#
-# Tautulli is free software: you can redistribute it and/or modify
+# PlexPy 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 3 of the License, or
# (at your option) any later version.
#
-# Tautulli is distributed in the hope that it will be useful,
+# PlexPy 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 Tautulli. If not, see .
+# along with PlexPy. If not, see .
-from httplib import HTTPSConnection
-from httplib import HTTPConnection
-import ssl
+from functools import partial
+from multiprocessing.dummy import Pool as ThreadPool
+from urlparse import urljoin
+
+import certifi
+from requests.packages import urllib3
+from requests.packages.urllib3.exceptions import InsecureRequestWarning
import plexpy
import helpers
@@ -30,94 +34,144 @@ class HTTPHandler(object):
Retrieve data from Plex Server
"""
- def __init__(self, host, port, token, ssl_verify=True):
- self.host = host
- self.port = str(port)
+ def __init__(self, urls, token=None, timeout=10, ssl_verify=True):
+ if isinstance(urls, basestring):
+ self.urls = urls.split() or urls.split(',')
+ else:
+ self.urls = urls
+
self.token = token
+ if self.token:
+ self.headers = {'X-Plex-Token': self.token}
+ else:
+ self.headers = {}
+
+ self.timeout = timeout
self.ssl_verify = ssl_verify
- """
- Handle the HTTP requests.
+ self.valid_request_types = ('GET', 'POST', 'PUT', 'DELETE')
- Output: object
- """
def make_request(self,
- uri=None, proto='HTTP',
- request_type='GET',
+ uri=None,
headers=None,
+ request_type='GET',
output_format='raw',
return_type=False,
no_token=False,
- timeout=None):
+ timeout=None,
+ callback=None):
+ """
+ Handle the HTTP requests.
- if timeout is None:
- timeout = plexpy.CONFIG.PMS_TIMEOUT
+ Output: list
+ """
- valid_request_types = ['GET', 'POST', 'PUT', 'DELETE']
+ self.uri = uri
+ self.request_type = request_type.upper()
+ self.output_format = output_format.lower()
+ self.return_type = return_type
+ self.callback = callback
+ self.timeout = timeout or self.timeout
- if request_type.upper() not in valid_request_types:
+ if self.request_type not in self.valid_request_types:
logger.debug(u"HTTP request made but unsupported request type given.")
return None
if uri:
- if proto.upper() == 'HTTPS':
- if not self.ssl_verify and hasattr(ssl, '_create_unverified_context'):
- context = ssl._create_unverified_context()
- handler = HTTPSConnection(host=self.host, port=self.port, timeout=timeout, context=context)
- logger.warn(u"Tautulli HTTP Handler :: Unverified HTTPS request made. This connection is not secure.")
- else:
- handler = HTTPSConnection(host=self.host, port=self.port, timeout=timeout)
- else:
- handler = HTTPConnection(host=self.host, port=self.port, timeout=timeout)
+ request_urls = [urljoin(url, self.uri) for url in self.urls]
- if not no_token:
- if headers:
- headers.update({'X-Plex-Token': self.token})
- else:
- headers = {'X-Plex-Token': self.token}
+ if no_token and headers:
+ self.headers = headers
+ elif headers:
+ self.headers.update(headers)
- try:
- if headers:
- handler.request(request_type, uri, headers=headers)
- else:
- handler.request(request_type, uri)
- response = handler.getresponse()
- request_status = response.status
- request_content = response.read()
- content_type = response.getheader('content-type')
- except IOError as e:
- logger.warn(u"Failed to access uri endpoint %s with error %s" % (uri, e))
- return None
- except Exception as e:
- logger.warn(u"Failed to access uri endpoint %s. Is your server maybe accepting SSL connections only? %s" % (uri, e))
- return None
- except:
- logger.warn(u"Failed to access uri endpoint %s with Uncaught exception." % uri)
- return None
+ responses = []
+ for r in self._http_requests_pool(request_urls):
+ responses.append(r)
- if request_status in (200, 201):
- try:
- if output_format == 'dict':
- output = helpers.convert_xml_to_dict(request_content)
- elif output_format == 'json':
- output = helpers.convert_xml_to_json(request_content)
- elif output_format == 'xml':
- output = helpers.parse_xml(request_content)
- else:
- output = request_content
+ return responses[0]
- if return_type:
- return output, content_type
-
- return output
-
- except Exception as e:
- logger.warn(u"Failed format response from uri %s to %s error %s" % (uri, output_format, e))
- return None
-
- else:
- logger.warn(u"Failed to access uri endpoint %s. Status code %r" % (uri, request_status))
- return None
else:
logger.debug(u"HTTP request made but no enpoint given.")
return None
+
+ def _http_requests_pool(self, urls, workers=10, chunk=None):
+ """Generator function to request urls in chunks"""
+ # From cpython
+ if chunk is None:
+ chunk, extra = divmod(len(urls), workers * 4)
+ if extra:
+ chunk += 1
+ if len(urls) == 0:
+ chunk = 0
+
+ if self.ssl_verify:
+ session = urllib3.PoolManager(cert_reqs='CERT_REQUIRED', ca_certs=certifi.where())
+ else:
+ urllib3.disable_warnings(InsecureRequestWarning)
+ session = urllib3.PoolManager()
+ part = partial(self._http_requests_urllib3, session=session)
+
+ if len(urls) == 1:
+ yield part(urls[0])
+ else:
+ pool = ThreadPool(workers)
+
+ try:
+ for work in pool.imap_unordered(part, urls, chunk):
+ yield work
+ except Exception as e:
+ logger.error(u"Failed to yield request: %s" % e)
+ finally:
+ pool.close()
+ pool.join()
+
+ def _http_requests_urllib3(self, url, session):
+ """Request the data from the url"""
+ try:
+ r = session.request(self.request_type, url, headers=self.headers, timeout=self.timeout)
+ except IOError as e:
+ logger.warn(u"Failed to access uri endpoint %s with error %s" % (self.uri, e))
+ return None
+ except Exception as e:
+ logger.warn(u"Failed to access uri endpoint %s. Is your server maybe accepting SSL connections only? %s" % (self.uri, e))
+ return None
+ except:
+ logger.warn(u"Failed to access uri endpoint %s with Uncaught exception." % self.uri)
+ return None
+
+ response_status = r.status
+ response_content = r.data
+ response_headers = r.headers
+
+ if response_status in (200, 201):
+ return self._http_format_output(response_content, response_headers)
+ else:
+ logger.warn(u"Failed to access uri endpoint %s. Status code %r" % (self.uri, response_status))
+ return None
+
+ def _http_format_output(self, response_content, response_headers):
+ """Formats the request response to the desired type"""
+ try:
+ if self.output_format == 'text':
+ output = response_content.decode('utf-8', 'ignore')
+ if self.output_format == 'dict':
+ output = helpers.convert_xml_to_dict(response_content.decode('utf-8', 'ignore'))
+ elif self.output_format == 'json':
+ output = helpers.convert_xml_to_json(response_content.decode('utf-8', 'ignore'))
+ elif self.output_format == 'xml':
+ output = helpers.parse_xml(response_content.decode('utf-8', 'ignore'))
+ else:
+ output = response_content
+
+ if self.callback:
+ return self.callback(output)
+
+ if self.return_type:
+ return output, response_headers['Content-Type']
+
+ return output
+
+ except Exception as e:
+ logger.warn(u"Failed format response from uri %s to %s error %s" % (self.uri, self.response_type, e))
+ return None
diff --git a/plexpy/libraries.py b/plexpy/libraries.py
index 2bc007ab..d43e1309 100644
--- a/plexpy/libraries.py
+++ b/plexpy/libraries.py
@@ -27,6 +27,66 @@ import pmsconnect
import session
+def refresh_libraries():
+ logger.info(u"Tautulli Libraries :: Requesting libraries list refresh...")
+
+ server_id = plexpy.CONFIG.PMS_IDENTIFIER
+ if not server_id:
+ logger.error(u"Tautulli Libraries :: No PMS identifier, cannot refresh libraries. Verify server in settings.")
+ return
+
+ library_sections = pmsconnect.PmsConnect().get_library_details()
+
+ if library_sections:
+ monitor_db = database.MonitorDatabase()
+
+ library_keys = []
+ new_keys = []
+
+ for section in library_sections:
+ section_keys = {'server_id': server_id,
+ 'section_id': section['section_id']}
+ section_values = {'server_id': server_id,
+ 'section_id': section['section_id'],
+ 'section_name': section['section_name'],
+ 'section_type': section['section_type'],
+ 'thumb': section['thumb'],
+ 'art': section['art'],
+ 'count': section['count'],
+ 'parent_count': section.get('parent_count', None),
+ 'child_count': section.get('child_count', None),
+ }
+
+ result = monitor_db.upsert('library_sections', key_dict=section_keys, value_dict=section_values)
+
+ library_keys.append(section['section_id'])
+
+ if result == 'insert':
+ new_keys.append(section['section_id'])
+
+ if plexpy.CONFIG.HOME_LIBRARY_CARDS == ['first_run_wizard']:
+ plexpy.CONFIG.__setattr__('HOME_LIBRARY_CARDS', library_keys)
+ plexpy.CONFIG.write()
+ else:
+ new_keys = plexpy.CONFIG.HOME_LIBRARY_CARDS + new_keys
+ plexpy.CONFIG.__setattr__('HOME_LIBRARY_CARDS', new_keys)
+ plexpy.CONFIG.write()
+
+ #if plexpy.CONFIG.UPDATE_SECTION_IDS == 1 or plexpy.CONFIG.UPDATE_SECTION_IDS == -1:
+ # # Start library section_id update on it's own thread
+ # threading.Thread(target=libraries.update_section_ids).start()
+
+ #if plexpy.CONFIG.UPDATE_LABELS == 1 or plexpy.CONFIG.UPDATE_LABELS == -1:
+ # # Start library labels update on it's own thread
+ # threading.Thread(target=libraries.update_labels).start()
+
+ logger.info(u"Tautulli Libraries :: Libraries list refreshed.")
+ return True
+ else:
+ logger.warn(u"Tautulli Libraries :: Unable to refresh libraries list.")
+ return False
+
+
def update_section_ids():
plexpy.CONFIG.UPDATE_SECTION_IDS = -1
@@ -965,7 +1025,7 @@ class Libraries(object):
monitor_db = database.MonitorDatabase()
# Refresh the PMS_URL to make sure the server_id is updated
- plextv.get_real_pms_url()
+ plextv.get_server_resources()
server_id = plexpy.CONFIG.PMS_IDENTIFIER
diff --git a/plexpy/plexivity_import.py b/plexpy/plexivity_import.py
index 226dfbc7..cdc1186b 100644
--- a/plexpy/plexivity_import.py
+++ b/plexpy/plexivity_import.py
@@ -23,7 +23,6 @@ import activity_processor
import database
import helpers
import logger
-import plextv
import users
@@ -284,7 +283,7 @@ def import_from_plexivity(database=None, table_name=None, import_ignore_interval
# Get the latest friends list so we can pull user id's
try:
- plextv.refresh_users()
+ users.refresh_users()
except:
logger.debug(u"Tautulli Importer :: Unable to refresh the users list. Aborting import.")
return None
diff --git a/plexpy/plextv.py b/plexpy/plextv.py
index 3782fc1c..7f75eb27 100644
--- a/plexpy/plextv.py
+++ b/plexpy/plextv.py
@@ -18,11 +18,9 @@
import base64
import json
-from xml.dom import minidom
import plexpy
import common
-import database
import helpers
import http_handler
import logger
@@ -31,129 +29,99 @@ import pmsconnect
import session
-def refresh_users():
- logger.info(u"Tautulli PlexTV :: Requesting users list refresh...")
- result = PlexTV().get_full_users_list()
+def get_server_resources(return_presence=False):
+ if not return_presence:
+ logger.info(u"Tautulli PlexTV :: Requesting resources for server...")
- monitor_db = database.MonitorDatabase()
- user_data = users.Users()
+ server = {'pms_name': plexpy.CONFIG.PMS_NAME,
+ 'pms_version': plexpy.CONFIG.PMS_VERSION,
+ 'pms_platform': plexpy.CONFIG.PMS_PLATFORM,
+ 'pms_ip': plexpy.CONFIG.PMS_IP,
+ 'pms_port': plexpy.CONFIG.PMS_PORT,
+ 'pms_ssl': plexpy.CONFIG.PMS_SSL,
+ 'pms_is_remote': plexpy.CONFIG.PMS_IS_REMOTE,
+ 'pms_is_cloud': plexpy.CONFIG.PMS_IS_CLOUD,
+ 'pms_url': plexpy.CONFIG.PMS_URL,
+ 'pms_url_manual': plexpy.CONFIG.PMS_URL_MANUAL
+ }
- if result:
- for item in result:
-
- shared_libraries = ''
- user_tokens = user_data.get_tokens(user_id=item['user_id'])
- if user_tokens and user_tokens['server_token']:
- pms_connect = pmsconnect.PmsConnect(token=user_tokens['server_token'])
- library_details = pms_connect.get_server_children()
-
- if library_details:
- shared_libraries = ';'.join(d['section_id'] for d in library_details['libraries_list'])
- else:
- shared_libraries = ''
-
- control_value_dict = {"user_id": item['user_id']}
- new_value_dict = {"username": item['username'],
- "thumb": item['thumb'],
- "email": item['email'],
- "is_home_user": item['is_home_user'],
- "is_allow_sync": item['is_allow_sync'],
- "is_restricted": item['is_restricted'],
- "shared_libraries": shared_libraries,
- "filter_all": item['filter_all'],
- "filter_movies": item['filter_movies'],
- "filter_tv": item['filter_tv'],
- "filter_music": item['filter_music'],
- "filter_photos": item['filter_photos']
- }
-
- # Check if we've set a custom avatar if so don't overwrite it.
- if item['user_id']:
- avatar_urls = monitor_db.select('SELECT thumb, custom_avatar_url '
- 'FROM users WHERE user_id = ?',
- [item['user_id']])
- if avatar_urls:
- if not avatar_urls[0]['custom_avatar_url'] or \
- avatar_urls[0]['custom_avatar_url'] == avatar_urls[0]['thumb']:
- new_value_dict['custom_avatar_url'] = item['thumb']
- else:
- new_value_dict['custom_avatar_url'] = item['thumb']
-
- monitor_db.upsert('users', new_value_dict, control_value_dict)
-
- logger.info(u"Tautulli PlexTV :: Users list refreshed.")
- return True
+ if server['pms_url_manual'] and server['pms_ssl'] or server['pms_is_cloud']:
+ scheme = 'https'
else:
- logger.warn(u"Tautulli PlexTV :: Unable to refresh users list.")
- return False
+ scheme = 'http'
-
-def get_real_pms_url():
- logger.info(u"Tautulli PlexTV :: Requesting URLs for server...")
-
- # Reset any current PMS_URL value
- plexpy.CONFIG.__setattr__('PMS_URL', '')
- plexpy.CONFIG.write()
-
- fallback_url = 'http://{}:{}'.format(plexpy.CONFIG.PMS_IP, plexpy.CONFIG.PMS_PORT)
+ fallback_url = '{scheme}://{hostname}:{port}'.format(scheme=scheme,
+ hostname=server['pms_ip'],
+ port=server['pms_port'])
plex_tv = PlexTV()
- result = plex_tv.get_server_urls(include_https=plexpy.CONFIG.PMS_SSL)
- plexpass = plex_tv.get_plexpass_status()
+ result = plex_tv.get_server_connections(pms_identifier=plexpy.CONFIG.PMS_IDENTIFIER,
+ pms_ip=server['pms_ip'],
+ pms_port=server['pms_port'],
+ include_https=server['pms_ssl'])
- connections = []
if result:
- plexpy.CONFIG.__setattr__('PMS_VERSION', result['version'])
- plexpy.CONFIG.__setattr__('PMS_PLATFORM', result['platform'])
- plexpy.CONFIG.__setattr__('PMS_PLEXPASS', plexpass)
- connections = result['connections']
+ connections = result.pop('connections', [])
+ server.update(result)
+ presence = server.pop('server_presence', 0)
+ else:
+ connections = []
+ presence = 0
+
+ if return_presence:
+ return presence
+
+ plexpass = plex_tv.get_plexpass_status()
+ server['pms_plexpass'] = int(plexpass)
# Only need to retrieve PMS_URL if using SSL
- if not plexpy.CONFIG.PMS_URL_MANUAL and plexpy.CONFIG.PMS_SSL:
+ if not server['pms_url_manual'] and server['pms_ssl']:
if connections:
- if plexpy.CONFIG.PMS_IS_REMOTE:
+ if server['pms_is_remote']:
# Get all remote connections
- conns = [c for c in connections if c['local'] == '0' and 'plex.direct' in c['uri']]
+ conns = [c for c in connections if
+ c['local'] == '0' and ('plex.direct' in c['uri'] or 'plex.service' in c['uri'])]
else:
# Get all local connections
- conns = [c for c in connections if c['local'] == '1' and 'plex.direct' in c['uri']]
+ conns = [c for c in connections if
+ c['local'] == '1' and ('plex.direct' in c['uri'] or 'plex.service' in c['uri'])]
if conns:
# Get connection with matching address, otherwise return first connection
- conn = next((c for c in conns if c['address'] == plexpy.CONFIG.PMS_IP
- and c['port'] == str(plexpy.CONFIG.PMS_PORT)), conns[0])
- plexpy.CONFIG.__setattr__('PMS_URL', conn['uri'])
- plexpy.CONFIG.write()
+ conn = next((c for c in conns if c['address'] == server['pms_ip']
+ and c['port'] == str(server['pms_port'])), conns[0])
+ server['pms_url'] = conn['uri']
logger.info(u"Tautulli PlexTV :: Server URL retrieved.")
# get_server_urls() failed or PMS_URL not found, fallback url doesn't use SSL
- if not plexpy.CONFIG.PMS_URL:
- plexpy.CONFIG.__setattr__('PMS_URL', fallback_url)
- plexpy.CONFIG.write()
+ if not server['pms_url']:
+ server['pms_url'] = fallback_url
logger.warn(u"Tautulli PlexTV :: Unable to retrieve server URLs. Using user-defined value without SSL.")
- # Not using SSL, remote has no effect
+ # Not using SSL, remote has no effect
else:
- if plexpy.CONFIG.PMS_URL_MANUAL and plexpy.CONFIG.PMS_SSL:
- fallback_url = fallback_url.replace('http://', 'https://')
-
- plexpy.CONFIG.__setattr__('PMS_URL', fallback_url)
- plexpy.CONFIG.write()
+ server['pms_url'] = fallback_url
logger.info(u"Tautulli PlexTV :: Using user-defined URL.")
+ plexpy.CONFIG.process_kwargs(server)
+ plexpy.CONFIG.write()
+
class PlexTV(object):
"""
Plex.tv authentication
"""
- def __init__(self, username='', password='', token=None):
- self.protocol = 'HTTPS'
+ def __init__(self, username=None, password=None, token=None):
self.username = username
self.password = password
+ self.token = token
+
+ self.urls = 'https://plex.tv'
+ self.timeout = plexpy.CONFIG.PMS_TIMEOUT
self.ssl_verify = plexpy.CONFIG.VERIFY_SSL_CERT
- if not token:
+ if not self.token:
# Check if we should use the admin token, or the guest server token
if session.get_session_user_id():
user_data = users.Users()
@@ -161,12 +129,14 @@ class PlexTV(object):
self.token = user_tokens['server_token']
else:
self.token = plexpy.CONFIG.PMS_TOKEN
- else:
- self.token = token
- self.request_handler = http_handler.HTTPHandler(host='plex.tv',
- port=443,
+ if not self.token:
+ logger.error(u"Tautulli PlexTV :: PlexTV called, but no token provided.")
+ return
+
+ self.request_handler = http_handler.HTTPHandler(urls=self.urls,
token=self.token,
+ timeout=self.timeout,
ssl_verify=self.ssl_verify)
def get_plex_auth(self, output_format='raw'):
@@ -183,7 +153,6 @@ class PlexTV(object):
}
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='POST',
headers=headers,
output_format=output_format,
@@ -265,7 +234,6 @@ class PlexTV(object):
def get_plextv_friends(self, output_format=''):
uri = '/api/users'
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -274,7 +242,6 @@ class PlexTV(object):
def get_plextv_user_details(self, output_format=''):
uri = '/users/account'
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -283,7 +250,6 @@ class PlexTV(object):
def get_plextv_devices_list(self, output_format=''):
uri = '/devices.xml'
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -292,7 +258,6 @@ class PlexTV(object):
def get_plextv_server_list(self, output_format=''):
uri = '/pms/servers.xml'
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -301,7 +266,6 @@ class PlexTV(object):
def get_plextv_sync_lists(self, machine_id='', output_format=''):
uri = '/servers/%s/sync_lists' % machine_id
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -313,7 +277,6 @@ class PlexTV(object):
else:
uri = '/api/resources'
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -325,7 +288,6 @@ class PlexTV(object):
else:
uri = '/api/downloads/1.json'
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -334,7 +296,6 @@ class PlexTV(object):
def delete_plextv_device(self, device_id='', output_format=''):
uri = '/devices/%s.xml' % device_id
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='DELETE',
output_format=output_format)
@@ -343,7 +304,6 @@ class PlexTV(object):
def delete_plextv_device_sync_lists(self, client_id='', output_format=''):
uri = '/devices/%s/sync_items' % client_id
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -352,197 +312,174 @@ class PlexTV(object):
def delete_plextv_sync(self, client_id='', sync_id='', output_format=''):
uri = '/devices/%s/sync_items/%s' % (client_id, sync_id)
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='DELETE',
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()
+ friends_list = self.get_plextv_friends(output_format='xml')
+ own_account = self.get_plextv_user_details(output_format='xml')
users_list = []
try:
- xml_parse = minidom.parseString(own_account)
+ xml_head = own_account.getElementsByTagName('user')
except Exception as e:
- logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_full_users_list own account: %s" % e)
- return []
- except:
- logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_full_users_list own account.")
- return []
+ logger.warn(u"Tautulli PlexTV :: Unable to parse own account XML for get_full_users_list: %s." % e)
+ return {}
- xml_head = xml_parse.getElementsByTagName('user')
- if not xml_head:
- logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_full_users_list.")
- else:
- for a in xml_head:
- own_details = {"user_id": helpers.get_xml_attr(a, 'id'),
- "username": helpers.get_xml_attr(a, 'username'),
- "thumb": helpers.get_xml_attr(a, 'thumb'),
- "email": helpers.get_xml_attr(a, 'email'),
- "is_home_user": helpers.get_xml_attr(a, 'home'),
- "is_allow_sync": None,
- "is_restricted": helpers.get_xml_attr(a, 'restricted'),
- "filter_all": helpers.get_xml_attr(a, 'filterAll'),
- "filter_movies": helpers.get_xml_attr(a, 'filterMovies'),
- "filter_tv": helpers.get_xml_attr(a, 'filterTelevision'),
- "filter_music": helpers.get_xml_attr(a, 'filterMusic'),
- "filter_photos": helpers.get_xml_attr(a, 'filterPhotos')
- }
+ for a in xml_head:
+ own_details = {"user_id": helpers.get_xml_attr(a, 'id'),
+ "username": helpers.get_xml_attr(a, 'username'),
+ "thumb": helpers.get_xml_attr(a, 'thumb'),
+ "email": helpers.get_xml_attr(a, 'email'),
+ "is_home_user": helpers.get_xml_attr(a, 'home'),
+ "is_allow_sync": None,
+ "is_restricted": helpers.get_xml_attr(a, 'restricted'),
+ "filter_all": helpers.get_xml_attr(a, 'filterAll'),
+ "filter_movies": helpers.get_xml_attr(a, 'filterMovies'),
+ "filter_tv": helpers.get_xml_attr(a, 'filterTelevision'),
+ "filter_music": helpers.get_xml_attr(a, 'filterMusic'),
+ "filter_photos": helpers.get_xml_attr(a, 'filterPhotos')
+ }
- users_list.append(own_details)
+ users_list.append(own_details)
try:
- xml_parse = minidom.parseString(friends_list)
+ xml_head = friends_list.getElementsByTagName('User')
except Exception as e:
- logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_full_users_list friends list: %s" % e)
- return []
- except:
- logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_full_users_list friends list.")
- return []
+ logger.warn(u"Tautulli PlexTV :: Unable to parse friends list XML for get_full_users_list: %s." % e)
+ return {}
- xml_head = xml_parse.getElementsByTagName('User')
- if not xml_head:
- logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_full_users_list.")
- else:
- for a in xml_head:
- friend = {"user_id": helpers.get_xml_attr(a, 'id'),
- "username": helpers.get_xml_attr(a, 'title'),
- "thumb": helpers.get_xml_attr(a, 'thumb'),
- "email": helpers.get_xml_attr(a, 'email'),
- "is_home_user": helpers.get_xml_attr(a, 'home'),
- "is_allow_sync": helpers.get_xml_attr(a, 'allowSync'),
- "is_restricted": helpers.get_xml_attr(a, 'restricted'),
- "filter_all": helpers.get_xml_attr(a, 'filterAll'),
- "filter_movies": helpers.get_xml_attr(a, 'filterMovies'),
- "filter_tv": helpers.get_xml_attr(a, 'filterTelevision'),
- "filter_music": helpers.get_xml_attr(a, 'filterMusic'),
- "filter_photos": helpers.get_xml_attr(a, 'filterPhotos')
- }
+ for a in xml_head:
+ friend = {"user_id": helpers.get_xml_attr(a, 'id'),
+ "username": helpers.get_xml_attr(a, 'title'),
+ "thumb": helpers.get_xml_attr(a, 'thumb'),
+ "email": helpers.get_xml_attr(a, 'email'),
+ "is_home_user": helpers.get_xml_attr(a, 'home'),
+ "is_allow_sync": helpers.get_xml_attr(a, 'allowSync'),
+ "is_restricted": helpers.get_xml_attr(a, 'restricted'),
+ "filter_all": helpers.get_xml_attr(a, 'filterAll'),
+ "filter_movies": helpers.get_xml_attr(a, 'filterMovies'),
+ "filter_tv": helpers.get_xml_attr(a, 'filterTelevision'),
+ "filter_music": helpers.get_xml_attr(a, 'filterMusic'),
+ "filter_photos": helpers.get_xml_attr(a, 'filterPhotos')
+ }
- users_list.append(friend)
+ users_list.append(friend)
return users_list
def get_synced_items(self, machine_id=None, client_id_filter=None, user_id_filter=None, rating_key_filter=None):
- sync_list = self.get_plextv_sync_lists(machine_id)
+ sync_list = self.get_plextv_sync_lists(machine_id, output_format='xml')
user_data = users.Users()
synced_items = []
try:
- xml_parse = minidom.parseString(sync_list)
+ xml_head = sync_list.getElementsByTagName('SyncList')
except Exception as e:
- logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_synced_items: %s" % e)
- return []
- except:
- logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_synced_items.")
- return []
+ logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_synced_items: %s." % e)
+ return {}
- xml_head = xml_parse.getElementsByTagName('SyncList')
+ for a in xml_head:
+ client_id = helpers.get_xml_attr(a, 'clientIdentifier')
- if not xml_head:
- logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_synced_items.")
- else:
- for a in xml_head:
- client_id = helpers.get_xml_attr(a, 'clientIdentifier')
+ # Filter by client_id
+ if client_id_filter and client_id_filter != client_id:
+ continue
- # Filter by client_id
- if client_id_filter and client_id_filter != client_id:
- continue
+ sync_id = helpers.get_xml_attr(a, 'id')
+ sync_device = a.getElementsByTagName('Device')
- sync_id = helpers.get_xml_attr(a, 'id')
- sync_device = a.getElementsByTagName('Device')
+ for device in sync_device:
+ device_user_id = helpers.get_xml_attr(device, 'userID')
+ try:
+ device_username = user_data.get_details(user_id=device_user_id)['username']
+ device_friendly_name = user_data.get_details(user_id=device_user_id)['friendly_name']
+ except:
+ device_username = ''
+ device_friendly_name = ''
+ device_name = helpers.get_xml_attr(device, 'name')
+ device_product = helpers.get_xml_attr(device, 'product')
+ device_product_version = helpers.get_xml_attr(device, 'productVersion')
+ device_platform = helpers.get_xml_attr(device, 'platform')
+ device_platform_version = helpers.get_xml_attr(device, 'platformVersion')
+ device_type = helpers.get_xml_attr(device, 'device')
+ device_model = helpers.get_xml_attr(device, 'model')
+ device_last_seen = helpers.get_xml_attr(device, 'lastSeenAt')
- for device in sync_device:
- device_user_id = helpers.get_xml_attr(device, 'userID')
- try:
- device_username = user_data.get_details(user_id=device_user_id)['username']
- device_friendly_name = user_data.get_details(user_id=device_user_id)['friendly_name']
- except:
- device_username = ''
- device_friendly_name = ''
- device_name = helpers.get_xml_attr(device, 'name')
- device_product = helpers.get_xml_attr(device, 'product')
- device_product_version = helpers.get_xml_attr(device, 'productVersion')
- device_platform = helpers.get_xml_attr(device, 'platform')
- device_platform_version = helpers.get_xml_attr(device, 'platformVersion')
- device_type = helpers.get_xml_attr(device, 'device')
- device_model = helpers.get_xml_attr(device, 'model')
- device_last_seen = helpers.get_xml_attr(device, 'lastSeenAt')
+ # Filter by user_id
+ if user_id_filter and user_id_filter != device_user_id:
+ continue
- # Filter by user_id
- if user_id_filter and user_id_filter != device_user_id:
- continue
+ for synced in a.getElementsByTagName('SyncItems'):
+ sync_item = synced.getElementsByTagName('SyncItem')
+ for item in sync_item:
- for synced in a.getElementsByTagName('SyncItems'):
- sync_item = synced.getElementsByTagName('SyncItem')
- for item in sync_item:
+ for location in item.getElementsByTagName('Location'):
+ clean_uri = helpers.get_xml_attr(location, 'uri').split('%2F')
- for location in item.getElementsByTagName('Location'):
- clean_uri = helpers.get_xml_attr(location, 'uri').split('%2F')
+ rating_key = next((clean_uri[(idx + 1) % len(clean_uri)]
+ for idx, item in enumerate(clean_uri) if item == 'metadata'), None)
- rating_key = next((clean_uri[(idx + 1) % len(clean_uri)]
- for idx, item in enumerate(clean_uri) if item == 'metadata'), None)
+ # Filter by rating_key
+ if rating_key_filter and rating_key_filter != rating_key:
+ continue
- # Filter by rating_key
- if rating_key_filter and rating_key_filter != rating_key:
- continue
+ sync_id = helpers.get_xml_attr(item, 'id')
+ sync_version = helpers.get_xml_attr(item, 'version')
+ sync_root_title = helpers.get_xml_attr(item, 'rootTitle')
+ sync_title = helpers.get_xml_attr(item, 'title')
+ sync_metadata_type = helpers.get_xml_attr(item, 'metadataType')
+ sync_content_type = helpers.get_xml_attr(item, 'contentType')
- sync_id = helpers.get_xml_attr(item, 'id')
- sync_version = helpers.get_xml_attr(item, 'version')
- sync_root_title = helpers.get_xml_attr(item, 'rootTitle')
- sync_title = helpers.get_xml_attr(item, 'title')
- sync_metadata_type = helpers.get_xml_attr(item, 'metadataType')
- sync_content_type = helpers.get_xml_attr(item, 'contentType')
+ for status in item.getElementsByTagName('Status'):
+ status_failure_code = helpers.get_xml_attr(status, 'failureCode')
+ status_failure = helpers.get_xml_attr(status, 'failure')
+ status_state = helpers.get_xml_attr(status, 'state')
+ status_item_count = helpers.get_xml_attr(status, 'itemsCount')
+ status_item_complete_count = helpers.get_xml_attr(status, 'itemsCompleteCount')
+ status_item_downloaded_count = helpers.get_xml_attr(status, 'itemsDownloadedCount')
+ status_item_ready_count = helpers.get_xml_attr(status, 'itemsReadyCount')
+ status_item_successful_count = helpers.get_xml_attr(status, 'itemsSuccessfulCount')
+ status_total_size = helpers.get_xml_attr(status, 'totalSize')
+ status_item_download_percent_complete = helpers.get_percent(
+ status_item_downloaded_count, status_item_count)
- for status in item.getElementsByTagName('Status'):
- status_failure_code = helpers.get_xml_attr(status, 'failureCode')
- status_failure = helpers.get_xml_attr(status, 'failure')
- status_state = helpers.get_xml_attr(status, 'state')
- status_item_count = helpers.get_xml_attr(status, 'itemsCount')
- status_item_complete_count = helpers.get_xml_attr(status, 'itemsCompleteCount')
- status_item_downloaded_count = helpers.get_xml_attr(status, 'itemsDownloadedCount')
- status_item_ready_count = helpers.get_xml_attr(status, 'itemsReadyCount')
- status_item_successful_count = helpers.get_xml_attr(status, 'itemsSuccessfulCount')
- status_total_size = helpers.get_xml_attr(status, 'totalSize')
- status_item_download_percent_complete = helpers.get_percent(
- status_item_downloaded_count, status_item_count)
+ for settings in item.getElementsByTagName('MediaSettings'):
+ settings_audio_boost = helpers.get_xml_attr(settings, 'audioBoost')
+ settings_music_bitrate = helpers.get_xml_attr(settings, 'musicBitrate')
+ settings_photo_quality = helpers.get_xml_attr(settings, 'photoQuality')
+ settings_photo_resolution = helpers.get_xml_attr(settings, 'photoResolution')
+ settings_video_quality = helpers.get_xml_attr(settings, 'videoQuality')
+ settings_video_resolution = helpers.get_xml_attr(settings, 'videoResolution')
- for settings in item.getElementsByTagName('MediaSettings'):
- settings_audio_boost = helpers.get_xml_attr(settings, 'audioBoost')
- settings_music_bitrate = helpers.get_xml_attr(settings, 'musicBitrate')
- settings_photo_quality = helpers.get_xml_attr(settings, 'photoQuality')
- settings_photo_resolution = helpers.get_xml_attr(settings, 'photoResolution')
- settings_video_quality = helpers.get_xml_attr(settings, 'videoQuality')
- settings_video_resolution = helpers.get_xml_attr(settings, 'videoResolution')
+ sync_details = {"device_name": helpers.sanitize(device_name),
+ "platform": helpers.sanitize(device_platform),
+ "username": helpers.sanitize(device_username),
+ "friendly_name": helpers.sanitize(device_friendly_name),
+ "user_id": device_user_id,
+ "root_title": helpers.sanitize(sync_root_title),
+ "title": helpers.sanitize(sync_title),
+ "metadata_type": sync_metadata_type,
+ "content_type": sync_content_type,
+ "rating_key": rating_key,
+ "state": status_state,
+ "item_count": status_item_count,
+ "item_complete_count": status_item_complete_count,
+ "item_downloaded_count": status_item_downloaded_count,
+ "item_downloaded_percent_complete": status_item_download_percent_complete,
+ "music_bitrate": settings_music_bitrate,
+ "photo_quality": settings_photo_quality,
+ "video_quality": settings_video_quality,
+ "total_size": status_total_size,
+ "failure": status_failure,
+ "client_id": client_id,
+ "sync_id": sync_id
+ }
- sync_details = {"device_name": helpers.sanitize(device_name),
- "platform": helpers.sanitize(device_platform),
- "username": helpers.sanitize(device_username),
- "friendly_name": helpers.sanitize(device_friendly_name),
- "user_id": device_user_id,
- "root_title": helpers.sanitize(sync_root_title),
- "title": helpers.sanitize(sync_title),
- "metadata_type": sync_metadata_type,
- "content_type": sync_content_type,
- "rating_key": rating_key,
- "state": status_state,
- "item_count": status_item_count,
- "item_complete_count": status_item_complete_count,
- "item_downloaded_count": status_item_downloaded_count,
- "item_downloaded_percent_complete": status_item_download_percent_complete,
- "music_bitrate": settings_music_bitrate,
- "photo_quality": settings_photo_quality,
- "video_quality": settings_video_quality,
- "total_size": status_total_size,
- "failure": status_failure,
- "client_id": client_id,
- "sync_id": sync_id
- }
-
- synced_items.append(sync_details)
+ synced_items.append(sync_details)
return session.filter_session_info(synced_items, filter_key='user_id')
@@ -550,27 +487,16 @@ class PlexTV(object):
logger.info(u"Tautulli PlexTV :: Deleting sync item '%s'." % sync_id)
self.delete_plextv_sync(client_id=client_id, sync_id=sync_id)
- def get_server_urls(self, include_https=True):
+ def get_server_connections(self, pms_identifier='', pms_ip='', pms_port=32400, include_https=True):
- if plexpy.CONFIG.PMS_IDENTIFIER:
- server_id = plexpy.CONFIG.PMS_IDENTIFIER
- else:
- logger.error(u"Tautulli PlexTV :: Unable to retrieve server identity.")
+ if not pms_identifier:
+ logger.error(u"Tautulli PlexTV :: Unable to retrieve server connections: no pms_identifier provided.")
return {}
- plextv_resources = self.get_plextv_resources(include_https=include_https)
-
+ plextv_resources = self.get_plextv_resources(include_https=include_https,
+ output_format='xml')
try:
- xml_parse = minidom.parseString(plextv_resources)
- except Exception as e:
- logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_server_urls: %s" % e)
- return {}
- except:
- logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_server_urls.")
- return {}
-
- try:
- xml_head = xml_parse.getElementsByTagName('Device')
+ xml_head = plextv_resources.getElementsByTagName('Device')
except Exception as e:
logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_server_urls: %s." % e)
return {}
@@ -580,16 +506,20 @@ class PlexTV(object):
conn = []
connections = device.getElementsByTagName('Connection')
- server = {"platform": helpers.get_xml_attr(device, 'platform'),
- "version": helpers.get_xml_attr(device, 'productVersion')
+ server = {'pms_identifier': helpers.get_xml_attr(device, 'clientIdentifier'),
+ 'pms_name': helpers.get_xml_attr(device, 'name'),
+ 'pms_version': helpers.get_xml_attr(device, 'productVersion'),
+ 'pms_platform': helpers.get_xml_attr(device, 'platform'),
+ 'pms_presence': helpers.get_xml_attr(device, 'presence'),
+ 'pms_is_cloud': 1 if helpers.get_xml_attr(device, 'platform') == 'Cloud' else 0
}
for c in connections:
- server_details = {"protocol": helpers.get_xml_attr(c, 'protocol'),
- "address": helpers.get_xml_attr(c, 'address'),
- "port": helpers.get_xml_attr(c, 'port'),
- "uri": helpers.get_xml_attr(c, 'uri'),
- "local": helpers.get_xml_attr(c, 'local')
+ server_details = {'protocol': helpers.get_xml_attr(c, 'protocol'),
+ 'address': helpers.get_xml_attr(c, 'address'),
+ 'port': helpers.get_xml_attr(c, 'port'),
+ 'uri': helpers.get_xml_attr(c, 'uri'),
+ 'local': helpers.get_xml_attr(c, 'local')
}
conn.append(server_details)
@@ -600,10 +530,10 @@ class PlexTV(object):
# Try to match the device
for a in xml_head:
- if helpers.get_xml_attr(a, 'clientIdentifier') == server_id:
+ if helpers.get_xml_attr(a, 'clientIdentifier') == pms_identifier:
server = get_connections(a)
break
-
+
# Else no device match found
if not server:
# Try to match the PMS_IP and PMS_PORT
@@ -612,15 +542,8 @@ class PlexTV(object):
connections = a.getElementsByTagName('Connection')
for connection in connections:
- if helpers.get_xml_attr(connection, 'address') == plexpy.CONFIG.PMS_IP and \
- int(helpers.get_xml_attr(connection, 'port')) == plexpy.CONFIG.PMS_PORT:
-
- plexpy.CONFIG.PMS_IDENTIFIER = helpers.get_xml_attr(a, 'clientIdentifier')
- plexpy.CONFIG.write()
-
- logger.info(u"Tautulli PlexTV :: PMS identifier changed from %s to %s."
- % (server_id, plexpy.CONFIG.PMS_IDENTIFIER))
-
+ if helpers.get_xml_attr(connection, 'address') == pms_ip and \
+ helpers.get_xml_attr(connection, 'port') == str(pms_port):
server = get_connections(a)
break
@@ -649,7 +572,7 @@ class PlexTV(object):
return server_times
- def discover(self, include_cloud=True):
+ def discover(self, include_cloud=True, all_servers=False):
""" Query plex for all servers online. Returns the ones you own in a selectize format """
servers = self.get_plextv_resources(include_https=True, output_format='xml')
clean_servers = []
@@ -679,15 +602,16 @@ class PlexTV(object):
connections = d.getElementsByTagName('Connection')
for c in connections:
- # If this is a remote server don't show any local IPs.
- if helpers.get_xml_attr(d, 'publicAddressMatches') == '0' and \
- helpers.get_xml_attr(c, 'local') == '1':
- continue
+ if not all_servers:
+ # If this is a remote server don't show any local IPs.
+ if helpers.get_xml_attr(d, 'publicAddressMatches') == '0' and \
+ helpers.get_xml_attr(c, 'local') == '1':
+ continue
- # If this is a local server don't show any remote IPs.
- if helpers.get_xml_attr(d, 'publicAddressMatches') == '1' and \
- helpers.get_xml_attr(c, 'local') == '0':
- continue
+ # If this is a local server don't show any remote IPs.
+ if helpers.get_xml_attr(d, 'publicAddressMatches') == '1' and \
+ helpers.get_xml_attr(c, 'local') == '0':
+ continue
server = {'httpsRequired': helpers.get_xml_attr(d, 'httpsRequired'),
'clientIdentifier': helpers.get_xml_attr(d, 'clientIdentifier'),
@@ -770,8 +694,6 @@ class PlexTV(object):
return True
else:
logger.debug(u"Tautulli PlexTV :: Plex Pass subscription not found.")
- plexpy.CONFIG.__setattr__('PMS_PLEXPASS', 0)
- plexpy.CONFIG.write()
return False
def get_devices_list(self):
diff --git a/plexpy/plexwatch_import.py b/plexpy/plexwatch_import.py
index 5ebc64f8..df5fa283 100644
--- a/plexpy/plexwatch_import.py
+++ b/plexpy/plexwatch_import.py
@@ -22,7 +22,6 @@ import activity_processor
import database
import helpers
import logger
-import plextv
import users
@@ -275,7 +274,7 @@ def import_from_plexwatch(database=None, table_name=None, import_ignore_interval
# Get the latest friends list so we can pull user id's
try:
- plextv.refresh_users()
+ users.refresh_users()
except:
logger.debug(u"Tautulli Importer :: Unable to refresh the users list. Aborting import.")
return None
diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py
index ff9af316..11e3e2c9 100644
--- a/plexpy/pmsconnect.py
+++ b/plexpy/pmsconnect.py
@@ -13,16 +13,12 @@
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see .
-import threading
import urllib
-from urlparse import urlparse
import plexpy
import common
-import database
import helpers
import http_handler
-import libraries
import logger
import plextv
import session
@@ -49,83 +45,23 @@ def get_server_friendly_name():
return server_name
-def refresh_libraries():
- logger.info(u"Tautulli Pmsconnect :: Requesting libraries list refresh...")
-
- server_id = plexpy.CONFIG.PMS_IDENTIFIER
- if not server_id:
- logger.error(u"Tautulli Pmsconnect :: No PMS identifier, cannot refresh libraries. Verify server in settings.")
- return
-
- library_sections = PmsConnect().get_library_details()
-
- if library_sections:
- monitor_db = database.MonitorDatabase()
-
- library_keys = []
- new_keys = []
-
- for section in library_sections:
- section_keys = {'server_id': server_id,
- 'section_id': section['section_id']}
- section_values = {'server_id': server_id,
- 'section_id': section['section_id'],
- 'section_name': section['section_name'],
- 'section_type': section['section_type'],
- 'thumb': section['thumb'],
- 'art': section['art'],
- 'count': section['count'],
- 'parent_count': section.get('parent_count', None),
- 'child_count': section.get('child_count', None),
- }
-
- result = monitor_db.upsert('library_sections', key_dict=section_keys, value_dict=section_values)
-
- library_keys.append(section['section_id'])
-
- if result == 'insert':
- new_keys.append(section['section_id'])
-
- if plexpy.CONFIG.HOME_LIBRARY_CARDS == ['first_run_wizard']:
- plexpy.CONFIG.__setattr__('HOME_LIBRARY_CARDS', library_keys)
- plexpy.CONFIG.write()
- else:
- new_keys = plexpy.CONFIG.HOME_LIBRARY_CARDS + new_keys
- plexpy.CONFIG.__setattr__('HOME_LIBRARY_CARDS', new_keys)
- plexpy.CONFIG.write()
-
- #if plexpy.CONFIG.UPDATE_SECTION_IDS == 1 or plexpy.CONFIG.UPDATE_SECTION_IDS == -1:
- # # Start library section_id update on it's own thread
- # threading.Thread(target=libraries.update_section_ids).start()
-
- #if plexpy.CONFIG.UPDATE_LABELS == 1 or plexpy.CONFIG.UPDATE_LABELS == -1:
- # # Start library labels update on it's own thread
- # threading.Thread(target=libraries.update_labels).start()
-
- logger.info(u"Tautulli Pmsconnect :: Libraries list refreshed.")
- return True
- else:
- logger.warn(u"Tautulli Pmsconnect :: Unable to refresh libraries list.")
- return False
-
-
class PmsConnect(object):
"""
Retrieve data from Plex Server
"""
- def __init__(self, token=None):
- 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:
- hostname = plexpy.CONFIG.PMS_IP
- port = plexpy.CONFIG.PMS_PORT
- self.protocol = 'http'
+ def __init__(self, url=None, token=None):
+ self.url = url
+ self.token = token
- if not token:
+ if not self.url and plexpy.CONFIG.PMS_URL:
+ self.url = plexpy.CONFIG.PMS_URL
+ elif not self.url:
+ self.url = 'http://{hostname}:{port}'.format(hostname=plexpy.CONFIG.PMS_IP,
+ port=plexpy.CONFIG.PMS_PORT)
+ self.timeout = plexpy.CONFIG.PMS_TIMEOUT
+
+ if not self.token:
# Check if we should use the admin token, or the guest server token
if session.get_session_user_id():
user_data = users.Users()
@@ -133,12 +69,10 @@ class PmsConnect(object):
self.token = user_tokens['server_token']
else:
self.token = plexpy.CONFIG.PMS_TOKEN
- else:
- self.token = token
- self.request_handler = http_handler.HTTPHandler(host=hostname,
- port=port,
- token=self.token)
+ self.request_handler = http_handler.HTTPHandler(urls=self.url,
+ token=self.token,
+ timeout=self.timeout)
def get_sessions(self, output_format=''):
"""
@@ -150,7 +84,6 @@ class PmsConnect(object):
"""
uri = '/status/sessions'
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -166,7 +99,6 @@ class PmsConnect(object):
"""
uri = '/status/sessions/terminate?sessionId=%s&reason=%s' % (session_id, reason)
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -183,7 +115,6 @@ class PmsConnect(object):
"""
uri = '/library/metadata/' + rating_key
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -200,7 +131,6 @@ class PmsConnect(object):
"""
uri = '/library/metadata/' + rating_key + '/children'
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -217,7 +147,6 @@ class PmsConnect(object):
"""
uri = '/library/recentlyAdded?X-Plex-Container-Start=%s&X-Plex-Container-Size=%s' % (start, count)
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -234,7 +163,6 @@ class PmsConnect(object):
"""
uri = '/library/sections/%s/recentlyAdded?X-Plex-Container-Start=%s&X-Plex-Container-Size=%s' % (section_id, start, count)
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -251,7 +179,6 @@ class PmsConnect(object):
"""
uri = '/library/metadata/' + rating_key + '/children'
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -285,7 +212,6 @@ class PmsConnect(object):
"""
uri = '/library/metadata/' + rating_key + '/allLeaves'
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -301,7 +227,6 @@ class PmsConnect(object):
"""
uri = '/servers'
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -317,7 +242,6 @@ class PmsConnect(object):
"""
uri = '/:/prefs'
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -333,7 +257,6 @@ class PmsConnect(object):
"""
uri = '/identity'
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -349,7 +272,6 @@ class PmsConnect(object):
"""
uri = '/library/sections'
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -368,7 +290,6 @@ class PmsConnect(object):
uri = '/library/sections/' + section_id + '/' + list_type + '?X-Plex-Container-Start=0' + count + sort_type + label_key
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -384,7 +305,6 @@ class PmsConnect(object):
"""
uri = '/library/sections/' + section_id + '/label'
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -401,7 +321,6 @@ class PmsConnect(object):
"""
uri = '/sync/items/' + sync_id
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -417,7 +336,6 @@ class PmsConnect(object):
"""
uri = '/sync/transcodeQueue'
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -433,7 +351,6 @@ class PmsConnect(object):
"""
uri = '/hubs/search?query=' + urllib.quote(query.encode('utf8')) + '&limit=' + limit + '&includeCollections=1'
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -449,7 +366,6 @@ class PmsConnect(object):
"""
uri = '/myplex/account'
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -465,7 +381,6 @@ class PmsConnect(object):
"""
uri = '/myplex/refreshReachability'
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='PUT')
return request
@@ -480,7 +395,6 @@ class PmsConnect(object):
"""
uri = '/updater/check?download=0'
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='PUT',
output_format=output_format)
@@ -496,7 +410,6 @@ class PmsConnect(object):
"""
uri = '/updater/status'
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -515,7 +428,6 @@ class PmsConnect(object):
"""
uri = '/hubs/home/recentlyAdded?X-Plex-Container-Start=%s&X-Plex-Container-Size=%s&type=%s' % (start, count, type)
request = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
output_format=output_format)
@@ -2334,7 +2246,6 @@ class PmsConnect(object):
uri = '/photo/:/transcode?%s' % urllib.urlencode(params)
result = self.request_handler.make_request(uri=uri,
- proto=self.protocol,
request_type='GET',
return_type=True)
diff --git a/plexpy/users.py b/plexpy/users.py
index a278f5fa..4c79939b 100644
--- a/plexpy/users.py
+++ b/plexpy/users.py
@@ -23,9 +23,67 @@ import datatables
import helpers
import logger
import plextv
+import pmsconnect
import session
+def refresh_users():
+ logger.info(u"Tautulli Users :: Requesting users list refresh...")
+ result = plextv.PlexTV().get_full_users_list()
+
+ monitor_db = database.MonitorDatabase()
+ user_data = Users()
+
+ if result:
+ for item in result:
+
+ shared_libraries = ''
+ user_tokens = user_data.get_tokens(user_id=item['user_id'])
+ if user_tokens and user_tokens['server_token']:
+ pms_connect = pmsconnect.PmsConnect(token=user_tokens['server_token'])
+ library_details = pms_connect.get_server_children()
+
+ if library_details:
+ shared_libraries = ';'.join(d['section_id'] for d in library_details['libraries_list'])
+ else:
+ shared_libraries = ''
+
+ control_value_dict = {"user_id": item['user_id']}
+ new_value_dict = {"username": item['username'],
+ "thumb": item['thumb'],
+ "email": item['email'],
+ "is_home_user": item['is_home_user'],
+ "is_allow_sync": item['is_allow_sync'],
+ "is_restricted": item['is_restricted'],
+ "shared_libraries": shared_libraries,
+ "filter_all": item['filter_all'],
+ "filter_movies": item['filter_movies'],
+ "filter_tv": item['filter_tv'],
+ "filter_music": item['filter_music'],
+ "filter_photos": item['filter_photos']
+ }
+
+ # Check if we've set a custom avatar if so don't overwrite it.
+ if item['user_id']:
+ avatar_urls = monitor_db.select('SELECT thumb, custom_avatar_url '
+ 'FROM users WHERE user_id = ?',
+ [item['user_id']])
+ if avatar_urls:
+ if not avatar_urls[0]['custom_avatar_url'] or \
+ avatar_urls[0]['custom_avatar_url'] == avatar_urls[0]['thumb']:
+ new_value_dict['custom_avatar_url'] = item['thumb']
+ else:
+ new_value_dict['custom_avatar_url'] = item['thumb']
+
+ monitor_db.upsert('users', new_value_dict, control_value_dict)
+
+ logger.info(u"Tautulli Users :: Users list refreshed.")
+ return True
+ else:
+ logger.warn(u"Tautulli Users :: Unable to refresh users list.")
+ return False
+
+
class Users(object):
def __init__(self):
@@ -360,7 +418,7 @@ class Users(object):
logger.warn(u"Tautulli Users :: Unable to retrieve user %s from database. Requesting user list refresh."
% user_id if user_id else user)
# Let's first refresh the user list to make sure the user isn't newly added and not in the db yet
- plextv.refresh_users()
+ refresh_users()
user_details = get_user_details(user_id=user_id, user=user)
diff --git a/plexpy/web_socket.py b/plexpy/web_socket.py
index 5163eba5..0aff6931 100644
--- a/plexpy/web_socket.py
+++ b/plexpy/web_socket.py
@@ -42,6 +42,7 @@ def start_thread():
def on_disconnect():
activity_processor.ActivityProcessor().set_temp_stopped()
+ plexpy.initialize_scheduler()
def reconnect():
@@ -148,9 +149,8 @@ def run():
logger.info(u"Tautulli WebSocket :: Unable to get an internal response from the server, Plex server is down.")
plexpy.NOTIFY_QUEUE.put({'notify_action': 'on_intdown'})
plexpy.PLEX_SERVER_UP = False
- on_disconnect()
- plexpy.initialize_scheduler()
+ on_disconnect()
logger.debug(u"Tautulli WebSocket :: Leaving thread.")
diff --git a/plexpy/webauth.py b/plexpy/webauth.py
index 350af0c6..3b1377bd 100644
--- a/plexpy/webauth.py
+++ b/plexpy/webauth.py
@@ -27,9 +27,8 @@ from hashing_passwords import check_hash
import plexpy
import logger
-import plextv
from plexpy.database import MonitorDatabase
-from plexpy.users import Users
+from plexpy.users import Users, refresh_users
from plexpy.plextv import PlexTV
@@ -72,7 +71,7 @@ def user_login(username=None, password=None):
if result:
# Refresh the users list to make sure we have all the correct permissions.
- plextv.refresh_users()
+ users.refresh_users()
# Successful login
return True
else:
diff --git a/plexpy/webserve.py b/plexpy/webserve.py
index c9f970d8..21dddd66 100644
--- a/plexpy/webserve.py
+++ b/plexpy/webserve.py
@@ -16,10 +16,8 @@
import hashlib
import json
import os
-import random
import shutil
import threading
-import uuid
import cherrypy
from cherrypy.lib.static import serve_file, serve_download
@@ -119,7 +117,7 @@ class WebInterface(object):
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
@addtoapi("get_server_list")
- def discover(self, token=None, include_cloud=True, **kwargs):
+ def discover(self, token=None, include_cloud=True, all_servers=False, **kwargs):
""" Get all your servers that are published to Plex.tv.
```
@@ -150,12 +148,14 @@ class WebInterface(object):
plexpy.CONFIG.write()
include_cloud = not (include_cloud == 'false')
+ all_servers = all_servers == 'true'
plex_tv = plextv.PlexTV()
- servers = plex_tv.discover(include_cloud=include_cloud)
+ servers_list = plex_tv.discover(include_cloud=include_cloud,
+ all_servers=all_servers)
- if servers:
- return servers
+ if servers_list:
+ return servers_list
##### Home #####
@@ -170,6 +170,7 @@ class WebInterface(object):
"home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT,
"home_stats_recently_added_count": plexpy.CONFIG.HOME_STATS_RECENTLY_ADDED_COUNT,
"pms_name": plexpy.CONFIG.PMS_NAME,
+ "pms_is_cloud": plexpy.CONFIG.PMS_IS_CLOUD,
"update_show_changelog": plexpy.CONFIG.UPDATE_SHOW_CHANGELOG
}
return serve_template(templatename="index.html", title="Home", config=config)
@@ -452,7 +453,7 @@ class WebInterface(object):
@requireAuth(member_of("admin"))
def refresh_libraries_list(self, **kwargs):
""" Refresh the libraries list on it's own thread. """
- threading.Thread(target=pmsconnect.refresh_libraries).start()
+ threading.Thread(target=libraries.refresh_libraries).start()
logger.info(u"Manual libraries list refresh requested.")
return True
@@ -1074,7 +1075,7 @@ class WebInterface(object):
@requireAuth(member_of("admin"))
def refresh_users_list(self, **kwargs):
""" Refresh the users list on it's own thread. """
- threading.Thread(target=plextv.refresh_users).start()
+ threading.Thread(target=users.refresh_users).start()
logger.info(u"Manual users list refresh requested.")
return True
@@ -2559,6 +2560,8 @@ class WebInterface(object):
"pms_port": plexpy.CONFIG.PMS_PORT,
"pms_token": plexpy.CONFIG.PMS_TOKEN,
"pms_ssl": checked(plexpy.CONFIG.PMS_SSL),
+ "pms_is_remote": checked(plexpy.CONFIG.PMS_IS_REMOTE),
+ "pms_is_cloud": plexpy.CONFIG.PMS_IS_CLOUD,
"pms_url_manual": checked(plexpy.CONFIG.PMS_URL_MANUAL),
"pms_uuid": plexpy.CONFIG.PMS_UUID,
"pms_web_url": plexpy.CONFIG.PMS_WEB_URL,
@@ -2576,7 +2579,6 @@ class WebInterface(object):
"refresh_users_interval": plexpy.CONFIG.REFRESH_USERS_INTERVAL,
"refresh_users_on_startup": checked(plexpy.CONFIG.REFRESH_USERS_ON_STARTUP),
"logging_ignore_interval": plexpy.CONFIG.LOGGING_IGNORE_INTERVAL,
- "pms_is_remote": checked(plexpy.CONFIG.PMS_IS_REMOTE),
"notify_consecutive": checked(plexpy.CONFIG.NOTIFY_CONSECUTIVE),
"notify_upload_posters": checked(plexpy.CONFIG.NOTIFY_UPLOAD_POSTERS),
"notify_recently_added_upgrade": checked(plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_UPGRADE),
@@ -2733,8 +2735,7 @@ class WebInterface(object):
# Get new server URLs for SSL communications and get new server friendly name
if server_changed:
- plextv.get_real_pms_url()
- pmsconnect.get_server_friendly_name()
+ plextv.get_server_resources()
web_socket.reconnect()
# If first run, start websocket
@@ -2751,11 +2752,11 @@ class WebInterface(object):
# Refresh users table if our server IP changes.
if refresh_libraries:
- threading.Thread(target=pmsconnect.refresh_libraries).start()
+ threading.Thread(target=libraries.refresh_libraries).start()
# Refresh users table if our server IP changes.
if refresh_users:
- threading.Thread(target=plextv.refresh_users).start()
+ threading.Thread(target=users.refresh_users).start()
return {'result': 'success', 'message': 'Settings saved.'}
@@ -3425,16 +3426,15 @@ class WebInterface(object):
# Fallback to checking /identity endpoint is server is unpublished
# Cannot set SSL settings on the PMS if unpublished so 'http' is okay
if not identifier:
- request_handler = http_handler.HTTPHandler(host=hostname,
- port=port,
- token=None)
+ scheme = 'https' if ssl else 'http'
+ url = '{scheme}://{hostname}:{port}'.format(scheme=scheme, hostname=hostname, port=port)
uri = '/identity'
+
+ request_handler = http_handler.HTTPHandler(urls=url,
+ ssl_verify=False)
request = request_handler.make_request(uri=uri,
- proto='http',
request_type='GET',
- output_format='xml',
- no_token=True,
- timeout=10)
+ output_format='xml')
if request:
xml_head = request.getElementsByTagName('MediaContainer')[0]
identifier = xml_head.getAttribute('machineIdentifier')