Merge branch 'dev'

# Conflicts:
#	data/interfaces/default/info.html
This commit is contained in:
Tim
2015-09-06 15:23:38 +02:00
25 changed files with 2678 additions and 1039 deletions

View File

@@ -588,6 +588,12 @@ def dbcheck():
'ALTER TABLE users ADD COLUMN custom_avatar_url TEXT'
)
# Add "Local" user to database as default unauthenticated user.
result = c_db.execute('SELECT id FROM users WHERE username = "Local"')
if not result.fetchone():
logger.debug(u'User "Local" does not exist. Adding user.')
c_db.execute('INSERT INTO users (user_id, username) VALUES (0, "Local")')
conn_db.commit()
c_db.close()

View File

@@ -1,4 +1,4 @@
import plexpy.logger
import plexpy.logger
import itertools
import os
import re
@@ -84,6 +84,7 @@ _CONFIG_DEFINITIONS = {
'GROWL_ON_WATCHED': (int, 'Growl', 0),
'HOME_STATS_LENGTH': (int, 'General', 30),
'HOME_STATS_TYPE': (int, 'General', 0),
'HOME_STATS_COUNT': (int, 'General', 5),
'HTTPS_CERT': (str, 'General', ''),
'HTTPS_KEY': (str, 'General', ''),
'HTTP_HOST': (str, 'General', '0.0.0.0'),

View File

@@ -55,6 +55,7 @@ class DataFactory(object):
(CASE WHEN session_history_metadata.duration IS NULL THEN 1.0 ELSE \
session_history_metadata.duration * 1.0 END) * 100) as percent_complete',
'session_history.grandparent_rating_key as grandparent_rating_key',
'session_history.parent_rating_key as parent_rating_key',
'session_history.rating_key as rating_key',
'session_history.user',
'session_history_metadata.media_type',
@@ -112,6 +113,7 @@ class DataFactory(object):
"duration": item["duration"],
"percent_complete": item["percent_complete"],
"grandparent_rating_key": item["grandparent_rating_key"],
"parent_rating_key": item["parent_rating_key"],
"rating_key": item["rating_key"],
"user": item["user"],
"media_type": item["media_type"],
@@ -129,7 +131,7 @@ class DataFactory(object):
return dict
def get_home_stats(self, time_range='30', stat_type='0'):
def get_home_stats(self, time_range='30', stat_type='0', stat_count='5'):
monitor_db = database.MonitorDatabase()
if not time_range.isdigit():
@@ -137,8 +139,11 @@ class DataFactory(object):
sort_type = 'total_plays' if stat_type == '0' else 'total_duration'
if not time_range.isdigit():
stat_count = '5'
# This actually determines the output order in the home page
stats_queries = ["top_tv", "popular_tv", "top_movies", "popular_movies", "top_users", "top_platforms"]
stats_queries = ["top_tv", "popular_tv", "top_movies", "popular_movies", "top_users", "top_platforms", "last_watched"]
home_stats = []
for stat in stats_queries:
@@ -161,7 +166,7 @@ class DataFactory(object):
'>= datetime("now", "-%s days", "localtime") ' \
'AND session_history_metadata.media_type = "episode" ' \
'GROUP BY session_history_metadata.grandparent_title ' \
'ORDER BY %s DESC LIMIT 10' % (time_range, sort_type)
'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stat_count)
result = monitor_db.select(query)
except:
logger.warn("Unable to execute database query.")
@@ -207,7 +212,7 @@ class DataFactory(object):
'>= datetime("now", "-%s days", "localtime") ' \
'AND session_history_metadata.media_type = "movie" ' \
'GROUP BY session_history_metadata.full_title ' \
'ORDER BY %s DESC LIMIT 10' % (time_range, sort_type)
'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stat_count)
result = monitor_db.select(query)
except:
logger.warn("Unable to execute database query.")
@@ -251,7 +256,7 @@ class DataFactory(object):
'AND session_history_metadata.media_type = "episode" ' \
'GROUP BY session_history_metadata.grandparent_title ' \
'ORDER BY users_watched DESC, total_plays DESC ' \
'LIMIT 10' % time_range
'LIMIT %s' % (time_range, stat_count)
result = monitor_db.select(query)
except:
logger.warn("Unable to execute database query.")
@@ -293,7 +298,7 @@ class DataFactory(object):
'AND session_history_metadata.media_type = "movie" ' \
'GROUP BY session_history_metadata.full_title ' \
'ORDER BY users_watched DESC, total_plays DESC ' \
'LIMIT 10' % time_range
'LIMIT %s' % (time_range, stat_count)
result = monitor_db.select(query)
except:
logger.warn("Unable to execute database query.")
@@ -338,7 +343,7 @@ class DataFactory(object):
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") >= ' \
'datetime("now", "-%s days", "localtime") '\
'GROUP BY session_history.user_id ' \
'ORDER BY %s DESC LIMIT 10' % (time_range, sort_type)
'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stat_count)
result = monitor_db.select(query)
except:
logger.warn("Unable to execute database query.")
@@ -356,7 +361,7 @@ class DataFactory(object):
'total_plays': item[2],
'total_duration': item[3],
'last_play': item[4],
'thumb': user_thumb,
'user_thumb': user_thumb,
'grandparent_thumb': '',
'users_watched': '',
'rating_key': '',
@@ -386,7 +391,7 @@ class DataFactory(object):
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \
'>= datetime("now", "-%s days", "localtime") ' \
'GROUP BY session_history.platform ' \
'ORDER BY total_plays DESC' % time_range
'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stat_count)
result = monitor_db.select(query)
except:
logger.warn("Unable to execute database query.")
@@ -413,6 +418,59 @@ class DataFactory(object):
'stat_type': sort_type,
'rows': top_platform})
elif 'last_watched' in stat:
last_watched = []
try:
query = 'SELECT session_history_metadata.id, ' \
'session_history.user, ' \
'(case when users.friendly_name is null then session_history.user else ' \
'users.friendly_name end) as friendly_name,' \
'users.user_id, ' \
'users.custom_avatar_url as user_thumb, ' \
'session_history_metadata.full_title, ' \
'session_history_metadata.rating_key, ' \
'session_history_metadata.thumb, ' \
'session_history_metadata.grandparent_thumb, ' \
'MAX(session_history.started) as last_watch, ' \
'session_history.player as platform ' \
'FROM session_history_metadata ' \
'JOIN session_history ON session_history_metadata.id = session_history.id ' \
'LEFT OUTER JOIN users ON session_history.user_id = users.user_id ' \
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \
'>= datetime("now", "-%s days", "localtime") ' \
'AND (session_history_metadata.media_type = "movie" ' \
'OR session_history_metadata.media_type = "episode") ' \
'GROUP BY session_history_metadata.full_title ' \
'ORDER BY last_watch DESC ' \
'LIMIT %s' % (time_range, stat_count)
result = monitor_db.select(query)
except:
logger.warn("Unable to execute database query.")
return None
for item in result:
if not item[8] or item[8] == '':
thumb = item[7]
else:
thumb = item[8]
row = {'row_id': item[0],
'user': item[1],
'friendly_name': item[2],
'user_id': item[3],
'user_thumb': item[4],
'title': item[5],
'rating_key': item[6],
'thumb': thumb,
'grandparent_thumb': item[8],
'last_watch': item[9],
'platform_type': item[10],
}
last_watched.append(row)
home_stats.append({'stat_id': stat,
'rows': last_watched})
return home_stats
def get_stream_details(self, row_id=None):

View File

@@ -27,9 +27,8 @@ def refresh_users():
if len(result) > 0:
for item in result:
control_value_dict = {"username": item['username']}
new_value_dict = {"user_id": item['user_id'],
"username": item['username'],
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'],

View File

@@ -89,6 +89,23 @@ class PmsConnect(object):
return request
"""
Return list of seasons in requested show.
Parameters required: rating_key { ratingKey of parent }
Optional parameters: output_format { dict, json }
Output: array
"""
def get_season_list(self, rating_key='', output_format=''):
uri = '/library/metadata/' + rating_key + '/children'
request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET',
output_format=output_format)
return request
"""
Return list of episodes in requested season.
@@ -103,7 +120,7 @@ class PmsConnect(object):
proto=self.protocol,
request_type='GET',
output_format=output_format)
return request
"""
@@ -154,6 +171,38 @@ class PmsConnect(object):
return request
"""
Return list of libraries on server.
Optional parameters: output_format { dict, json }
Output: array
"""
def get_libraries_list(self, output_format=''):
uri = '/library/sections'
request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET',
output_format=output_format)
return request
"""
Return list of items in library on server.
Optional parameters: output_format { dict, json }
Output: array
"""
def get_library_list(self, section_key='', list_type='all', count='0', sort_type='', output_format=''):
uri = '/library/sections/' + section_key + '/' + list_type +'?X-Plex-Container-Start=0&X-Plex-Container-Size=' + count + sort_type
request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET',
output_format=output_format)
return request
"""
Return sync item details.
@@ -552,6 +601,7 @@ class PmsConnect(object):
'user': user_details['username'],
'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['thumb'],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id,
@@ -661,6 +711,7 @@ class PmsConnect(object):
'user': user_details['username'],
'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['thumb'],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id,
@@ -668,6 +719,7 @@ class PmsConnect(object):
'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'),
'parent_title': helpers.get_xml_attr(session, 'parentTitle'),
'title': helpers.get_xml_attr(session, 'title'),
'year': helpers.get_xml_attr(session, 'year'),
'rating_key': helpers.get_xml_attr(session, 'ratingKey'),
'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'),
'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'),
@@ -693,9 +745,13 @@ class PmsConnect(object):
'duration': duration,
'progress': progress,
'progress_percent': str(helpers.get_percent(progress, duration)),
'type': helpers.get_xml_attr(session, 'type'),
'indexes': use_indexes
}
if helpers.get_xml_attr(session, 'ratingKey').isdigit():
session_output['type'] = helpers.get_xml_attr(session, 'type')
else:
session_output['type'] = 'clip'
elif helpers.get_xml_attr(session, 'type') == 'movie':
session_output = {'session_key': helpers.get_xml_attr(session, 'sessionKey'),
'media_index': helpers.get_xml_attr(session, 'index'),
@@ -708,6 +764,7 @@ class PmsConnect(object):
'user': user_details['username'],
'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['thumb'],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id,
@@ -715,6 +772,7 @@ class PmsConnect(object):
'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'),
'parent_title': helpers.get_xml_attr(session, 'parentTitle'),
'title': helpers.get_xml_attr(session, 'title'),
'year': helpers.get_xml_attr(session, 'year'),
'rating_key': helpers.get_xml_attr(session, 'ratingKey'),
'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'),
'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'),
@@ -740,9 +798,13 @@ class PmsConnect(object):
'duration': duration,
'progress': progress,
'progress_percent': str(helpers.get_percent(progress, duration)),
'type': helpers.get_xml_attr(session, 'type'),
'indexes': use_indexes
}
if helpers.get_xml_attr(session, 'ratingKey').isdigit():
session_output['type'] = helpers.get_xml_attr(session, 'type')
else:
session_output['type'] = 'clip'
elif helpers.get_xml_attr(session, 'type') == 'clip':
session_output = {'session_key': helpers.get_xml_attr(session, 'sessionKey'),
'media_index': helpers.get_xml_attr(session, 'index'),
@@ -755,6 +817,7 @@ class PmsConnect(object):
'user': user_details['username'],
'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['thumb'],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id,
@@ -762,6 +825,7 @@ class PmsConnect(object):
'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'),
'parent_title': helpers.get_xml_attr(session, 'parentTitle'),
'title': helpers.get_xml_attr(session, 'title'),
'year': helpers.get_xml_attr(session, 'year'),
'rating_key': helpers.get_xml_attr(session, 'ratingKey'),
'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'),
'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'),
@@ -795,6 +859,49 @@ class PmsConnect(object):
return session_output
"""
Return processed and validated season list.
Output: array
"""
def get_show_children(self, rating_key=''):
season_data = self.get_season_list(rating_key, output_format='xml')
try:
xml_head = season_data.getElementsByTagName('MediaContainer')
except:
logger.warn("Unable to parse XML for get_season_list.")
return []
season_list = []
for a in xml_head:
if a.getAttribute('size'):
if a.getAttribute('size') == '0':
logger.debug(u"No season data.")
season_list = {'season_count': '0',
'season_list': []
}
return season_list
if a.getElementsByTagName('Directory'):
result_data = a.getElementsByTagName('Directory')
for result in result_data:
season_output = {'rating_key': helpers.get_xml_attr(result, 'ratingKey'),
'index': helpers.get_xml_attr(result, 'index'),
'title': helpers.get_xml_attr(result, 'title'),
'thumb': helpers.get_xml_attr(result, 'thumb'),
'parent_thumb': helpers.get_xml_attr(a, 'thumb')
}
season_list.append(season_output)
output = {'season_count': helpers.get_xml_attr(xml_head[0], 'size'),
'title': helpers.get_xml_attr(xml_head[0], 'title2'),
'season_list': season_list
}
return output
"""
Return processed and validated episode list.
@@ -914,6 +1021,150 @@ class PmsConnect(object):
logger.debug(u"Server preferences queried but no parameter received.")
return None
"""
Return processed and validated server libraries list.
Output: array
"""
def get_server_children(self):
libraries_data = self.get_libraries_list(output_format='xml')
try:
xml_head = libraries_data.getElementsByTagName('MediaContainer')
except:
logger.warn("Unable to parse XML for get_libraries_list.")
return []
libraries_list = []
for a in xml_head:
if a.getAttribute('size'):
if a.getAttribute('size') == '0':
logger.debug(u"No libraries data.")
libraries_list = {'libraries_count': '0',
'libraries_list': []
}
return libraries_list
if a.getElementsByTagName('Directory'):
result_data = a.getElementsByTagName('Directory')
for result in result_data:
libraries_output = {'key': helpers.get_xml_attr(result, 'key'),
'type': helpers.get_xml_attr(result, 'type'),
'title': helpers.get_xml_attr(result, 'title'),
'thumb': helpers.get_xml_attr(result, 'thumb')
}
libraries_list.append(libraries_output)
output = {'libraries_count': helpers.get_xml_attr(xml_head[0], 'size'),
'title': helpers.get_xml_attr(xml_head[0], 'title1'),
'libraries_list': libraries_list
}
return output
"""
Return processed and validated server library items list.
Parameters required: library_type { movie, show, episode, artist }
section_key { unique library key }
Output: array
"""
def get_library_children(self, library_type='', section_key='', list_type='all', sort_type = ''):
# Currently only grab the library with 1 items so 'size' is not 0
count = '1'
if library_type == 'movie':
sort_type = '&type=1'
elif library_type == 'show':
sort_type = '&type=2'
elif library_type == 'episode':
sort_type = '&type=4'
elif library_type == 'album':
list_type = 'albums'
library_data = self.get_library_list(section_key, list_type, count, sort_type, output_format='xml')
try:
xml_head = library_data.getElementsByTagName('MediaContainer')
except:
logger.warn("Unable to parse XML for get_library_children.")
return []
library_list = []
for a in xml_head:
if a.getAttribute('size'):
if a.getAttribute('size') == '0':
logger.debug(u"No library data.")
library_list = {'library_count': '0',
'library_list': []
}
return library_list
if a.getElementsByTagName('Directory'):
result_data = a.getElementsByTagName('Directory')
for result in result_data:
library_output = {'key': helpers.get_xml_attr(result, 'key'),
'type': helpers.get_xml_attr(result, 'type'),
'title': helpers.get_xml_attr(result, 'title'),
'thumb': helpers.get_xml_attr(result, 'thumb')
}
library_list.append(library_output)
output = {'library_count': helpers.get_xml_attr(xml_head[0], 'totalSize'),
'count_type': helpers.get_xml_attr(xml_head[0], 'title2'),
'library_list': library_list
}
return output
"""
Return processed and validated server statistics.
Output: array
"""
def get_library_stats(self):
server_libraries = self.get_server_children()
server_library_stats = []
if server_libraries['libraries_count'] != '0':
libraries_list = server_libraries['libraries_list']
for library in libraries_list:
library_type = library['type']
section_key = library['key']
library_list = self.get_library_children(library_type, section_key)
if library_list['library_count'] != '0':
library_stats = {'title': library['title'],
'thumb': library['thumb'],
'count': library_list['library_count'],
'count_type': library_list['count_type']
}
if library_type == 'show':
episode_list = self.get_library_children(library_type='episode', section_key=section_key)
episode_stats = {'episode_count': episode_list['library_count'],
'episode_count_type': 'All Episodes'
}
library_stats.update(episode_stats)
if library_type == 'artist':
album_list = self.get_library_children(library_type='album', section_key=section_key)
album_stats = {'album_count': album_list['library_count'],
'album_count_type': 'All Albums'
}
library_stats.update(album_stats)
server_library_stats.append({'type': library_type,
'rows': library_stats})
return server_library_stats
"""
Return image data as array.
Array contains the image content type and image binary

View File

@@ -1,2 +1,2 @@
PLEXPY_VERSION = "master"
PLEXPY_RELEASE_VERSION = "1.1.5"
PLEXPY_RELEASE_VERSION = "1.1.6"

View File

@@ -1,4 +1,4 @@
# This file is part of PlexPy.
# This file is part of PlexPy.
#
# PlexPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -66,7 +66,9 @@ class WebInterface(object):
def home(self):
config = {
"home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH,
"home_stats_type": plexpy.CONFIG.HOME_STATS_TYPE
"home_stats_type": plexpy.CONFIG.HOME_STATS_TYPE,
"home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT,
"pms_identifier": plexpy.CONFIG.PMS_IDENTIFIER,
}
return serve_template(templatename="index.html", title="Home", config=config)
@@ -119,12 +121,19 @@ class WebInterface(object):
return json.dumps(formats)
@cherrypy.expose
def home_stats(self, time_range='30', stat_type='0', **kwargs):
def home_stats(self, time_range='30', stat_type='0', stat_count='5', **kwargs):
data_factory = datafactory.DataFactory()
stats_data = data_factory.get_home_stats(time_range=time_range, stat_type=stat_type)
stats_data = data_factory.get_home_stats(time_range=time_range, stat_type=stat_type, stat_count=stat_count)
return serve_template(templatename="home_stats.html", title="Stats", data=stats_data)
@cherrypy.expose
def library_stats(self, **kwargs):
pms_connect = pmsconnect.PmsConnect()
stats_data = pms_connect.get_library_stats()
return serve_template(templatename="library_stats.html", title="Library Stats", data=stats_data)
@cherrypy.expose
def history(self):
return serve_template(templatename="history.html", title="History")
@@ -453,6 +462,7 @@ class WebInterface(object):
"notify_on_watched_body_text": plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT,
"home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH,
"home_stats_type": checked(plexpy.CONFIG.HOME_STATS_TYPE),
"home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT,
"buffer_threshold": plexpy.CONFIG.BUFFER_THRESHOLD,
"buffer_wait": plexpy.CONFIG.BUFFER_WAIT
}
@@ -567,6 +577,9 @@ class WebInterface(object):
if 'rating_key' in kwargs:
rating_key = kwargs.get('rating_key', "")
custom_where = [['rating_key', rating_key]]
if 'parent_rating_key' in kwargs:
rating_key = kwargs.get('parent_rating_key', "")
custom_where = [['parent_rating_key', rating_key]]
if 'grandparent_rating_key' in kwargs:
rating_key = kwargs.get('grandparent_rating_key', "")
custom_where = [['grandparent_rating_key', rating_key]]
@@ -747,16 +760,19 @@ class WebInterface(object):
@cherrypy.expose
def info(self, item_id=None, source=None, **kwargs):
metadata = None
if source == 'history':
data_factory = datafactory.DataFactory()
result = data_factory.get_metadata_details(row_id=item_id)
metadata = data_factory.get_metadata_details(row_id=item_id)
else:
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_metadata_details(rating_key=item_id)['metadata']
result = pms_connect.get_metadata_details(rating_key=item_id)
if result:
metadata = result['metadata']
if result:
return serve_template(templatename="info.html", data=result, title="Info")
if metadata:
return serve_template(templatename="info.html", data=metadata, title="Info")
else:
logger.warn('Unable to retrieve data.')
return serve_template(templatename="info.html", data=None, title="Info")
@@ -801,7 +817,19 @@ class WebInterface(object):
return serve_template(templatename="user_platform_stats.html", data=None, title="Platform Stats")
@cherrypy.expose
def get_children(self, rating_key='', **kwargs):
def get_show_children(self, rating_key='', **kwargs):
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_show_children(rating_key)
if result:
return serve_template(templatename="info_season_list.html", data=result, title="Season List")
else:
logger.warn('Unable to retrieve data.')
return serve_template(templatename="info_season_list.html", data=None, title="Season List")
@cherrypy.expose
def get_season_children(self, rating_key='', **kwargs):
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_season_children(rating_key)