Merge branch 'dev'

This commit is contained in:
JonnyWong16 2016-05-20 20:54:36 -07:00
commit c3378e1653
19 changed files with 169 additions and 125 deletions

1
API.md
View file

@ -1061,6 +1061,7 @@ Required parameters:
count (str): Number of items to return count (str): Number of items to return
Optional parameters: Optional parameters:
start (str): The item number to start at
section_id (str): The id of the Plex library section section_id (str): The id of the Plex library section
Returns: Returns:

View file

@ -1,5 +1,25 @@
# Changelog # Changelog
## v1.4.1 (2016-05-20)
* New: HTTP Proxy checkbox in the settings. Enable this if using an SSL enabled reverse proxy in front of PlexPy.
* Fix: Check for blank username/password on login.
* Fix: Persist current activity artwork blur across refreshes when transcoding details are visible.
* Fix: Send notifications to multiple XBMC/Plex Home Theater devices.
* Fix: Reset PMS identifier when clicking verify server button in settings.
* Fix: Crash when trying to group current activity session in database.
* Fix: Check current activity returns sessions when refreshing.
* Fix: Logs sorted out of order.
* Fix: Resolution reported incorrectly in the stream info modal.
* Fix: PlexPy crashing when hashing password in the config file.
* Fix: CherryPy doubling the port number when accessing PlexPy locally with http_proxy enabled.
* Change: Sort by most recent for ties in watch statistics.
* Change: Refresh Join devices when changing the API key.
* Change: Format the Join device IDs.
* Change: Join notifications now sent with Python Requests module.
* Change: Add paging for recently added in the API.
## v1.4.0 (2016-05-15) ## v1.4.0 (2016-05-15)
* New: An HTML form login page with sessions support. * New: An HTML form login page with sessions support.

View file

@ -2955,3 +2955,13 @@ a.no-highlight:hover {
min-width: 150px; min-width: 150px;
max-width: 250px; max-width: 250px;
} }
.inline-pre {
font-family: monospace;
margin: 0 2px;
padding: 2px 5px;
font-size: 13px;
color: #fff;
background-color: #555;
border: 0px solid #444;
border-radius: 3px;
}

View file

@ -71,7 +71,7 @@ DOCUMENTATION :: END
% else: % else:
<a href="#"> <a href="#">
% endif % endif
<div class="dashboard-activity-poster"> <div class="dashboard-activity-poster" id="poster-${data['session_key']}">
% if not data['art'].startswith('interfaces') or not data['thumb'].startswith('interfaces'): % if not data['art'].startswith('interfaces') or not data['thumb'].startswith('interfaces'):
% if (data['media_type'] == 'movie' and not data['indexes']) or (data['indexes'] and not data['view_offset']): % if (data['media_type'] == 'movie' and not data['indexes']) or (data['indexes'] and not data['view_offset']):
<div id="bif-${data['session_key']}" class="dashboard-activity-poster-face" style="background-image: url(pms_image_proxy?img=${data['art']}&width=500&height=280&fallback=art);"></div> <div id="bif-${data['session_key']}" class="dashboard-activity-poster-face" style="background-image: url(pms_image_proxy?img=${data['art']}&width=500&height=280&fallback=art);"></div>
@ -105,7 +105,7 @@ DOCUMENTATION :: END
<div class="dashboard-activity-poster-face" style="background-image: url(${data['art']});"></div> <div class="dashboard-activity-poster-face" style="background-image: url(${data['art']});"></div>
% endif % endif
<div class="dashboard-activity-button-info"> <div class="dashboard-activity-button-info">
<button type="button" class="btn btn-activity-info btn-lg" data-target="#stream-${data['session_key']}"> <button type="button" class="btn btn-activity-info btn-lg" data-target="#stream-${data['session_key']}" data-id="${data['session_key']}">
<i class="fa fa-info-circle"></i> <i class="fa fa-info-circle"></i>
</button> </button>
</div> </div>

View file

@ -107,6 +107,11 @@
$('#dashboard-checking-activity').remove(); $('#dashboard-checking-activity').remove();
var current_activity = $.parseJSON(xhr.responseText); var current_activity = $.parseJSON(xhr.responseText);
if (!(current_activity)) {
$('#currentActivity').html('<div id="dashboard-no-activity" class="text-muted">There was an error communicating with your Plex Server.</div>');
return
}
var stream_count = parseInt(current_activity.stream_count); var stream_count = parseInt(current_activity.stream_count);
var sessions = current_activity.sessions; var sessions = current_activity.sessions;
@ -150,6 +155,7 @@
bif_poster.animate({ opacity: 0 }, { duration: 1000, queue: false }); bif_poster.animate({ opacity: 0 }, { duration: 1000, queue: false });
bif_poster.after($('<div id="bif-' + key + '"class="dashboard-activity-poster-face" style="background-image: url(pms_image_proxy?img=' bif_poster.after($('<div id="bif-' + key + '"class="dashboard-activity-poster-face" style="background-image: url(pms_image_proxy?img='
+ s.bif_thumb + '&width=500&height=280&fallback=art);"></div>').fadeIn(1000, function () { bif_poster.remove() })); + s.bif_thumb + '&width=500&height=280&fallback=art);"></div>').fadeIn(1000, function () { bif_poster.remove() }));
blurArtwork(key);
} }
// if transcoding, update the transcode state // if transcoding, update the transcode state
@ -210,14 +216,18 @@
getCurrentActivity(); getCurrentActivity();
}, 15000); }, 15000);
function blurArtwork(session_key) {
var filterVal = $('#stream-' + session_key).is(':visible') ? 'blur(5px)' : '';
$($('#poster-' + session_key).find('.dashboard-activity-poster-face, .dashboard-activity-cover-face'))
.css('filter', filterVal).css('webkitFilter', filterVal).css('mozFilter', filterVal).css('oFilter', filterVal).css('msFilter', filterVal);
}
// Show/Hide activity info // Show/Hide activity info
$('#currentActivity').on('click', '.btn-activity-info', function (e) { $('#currentActivity').on('click', '.btn-activity-info', function (e) {
e.preventDefault(); e.preventDefault();
$($(this).attr('data-target')).toggle(); $($(this).attr('data-target')).toggle();
var id = $(this).closest('.dashboard-instance').data('id'); var key = $(this).data('id');
var filterVal = $('#stream-' + id).is(':visible') ? 'blur(5px)' : ''; blurArtwork(key);
$($(this).closest('.dashboard-activity-poster').find('.dashboard-activity-poster-face, .dashboard-activity-cover-face'))
.css('filter',filterVal).css('webkitFilter',filterVal).css('mozFilter',filterVal).css('oFilter',filterVal).css('msFilter',filterVal);
}); });
// Add hover class to dashboard-instance // Add hover class to dashboard-instance

View file

@ -217,7 +217,7 @@
} }
} }
$('#pushbullet_apikey, #pushover_apitoken, #scripts_folder').on('change', function () { $('#pushbullet_apikey, #pushover_apitoken, #scripts_folder, #join_apikey').on('change', function () {
// Reload modal to update certain fields // Reload modal to update certain fields
doAjaxCall('set_notification_config', $(this), 'tabs', true, reloadModal); doAjaxCall('set_notification_config', $(this), 'tabs', true, reloadModal);
return false; return false;

View file

@ -109,6 +109,5 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
% else: % else:
<div class="text-muted">There was an error communicating with your Plex Server. Please check your <a href="settings">settings</a>. <div class="text-muted">There was an error communicating with your Plex Server.</div><br>
</div><br>
% endif % endif

View file

@ -432,6 +432,13 @@
</div> </div>
<p class="help-block">The base URL of the web server. Used for reverse proxies.</p> <p class="help-block">The base URL of the web server. Used for reverse proxies.</p>
</div> </div>
<div class="checkbox">
<label>
<input type="checkbox" name="http_proxy http-settings" id="http_proxy" value="1" ${config['http_proxy']}> Enable HTTP Proxy
</label>
<p class="help-block">Respect the X-Forwarded-Proto header. Used for reverse proxies with SSL.</p>
</div>
<br />
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" name="launch_browser" id="launch_browser" value="1" ${config['launch_browser']}> Launch Browser on Startup <input type="checkbox" name="launch_browser" id="launch_browser" value="1" ${config['launch_browser']}> Launch Browser on Startup
@ -2234,7 +2241,6 @@ $(document).ready(function() {
$( ".pms-settings" ).change(function() { $( ".pms-settings" ).change(function() {
serverChanged = true; serverChanged = true;
$("#pms_identifier").val(""); $("#pms_identifier").val("");
$("#pms-verify-status").html("");
$("#server_changed").prop('checked', true); $("#server_changed").prop('checked', true);
verifyServer(); verifyServer();
}); });
@ -2287,6 +2293,7 @@ $(document).ready(function() {
} }
$('#verify_server_button').on('click', function(){ $('#verify_server_button').on('click', function(){
$("#pms_identifier").val("");
verifyServer(); verifyServer();
}); });

View file

@ -59,7 +59,7 @@ DOCUMENTATION :: END
<ul class="list-unstyled"> <ul class="list-unstyled">
<li>Container: <strong>${data['transcode_container'] if data['transcode_container'] else data['container']}</strong></li> <li>Container: <strong>${data['transcode_container'] if data['transcode_container'] else data['container']}</strong></li>
% if data['media_type'] != 'track': % if data['media_type'] != 'track':
<li>Resolution: <strong>${data['video_resolution'] + 'p' if data['video_resolution'] != 'sd' else data['video_resolution']}</strong></li> <li>Resolution: <strong>${data['transcode_height'] if data['transcode_height'] else data['height']}p</strong></li>
% endif % endif
</ul> </ul>
</div> </div>
@ -101,7 +101,7 @@ DOCUMENTATION :: END
<ul class="list-unstyled"> <ul class="list-unstyled">
<li>Container: <strong>${data['container']}</strong></li> <li>Container: <strong>${data['container']}</strong></li>
% if data['media_type'] != 'track': % if data['media_type'] != 'track':
<li>Resolution: <strong>${data['height']}p</strong></li> <li>Resolution: <strong>${data['video_resolution'] + 'p' if data['video_resolution'] != 'sd' else data['video_resolution']}</strong></li>
% endif % endif
<li>Bitrate: <strong>${data['bitrate']} kbps</strong></li> <li>Bitrate: <strong>${data['bitrate']} kbps</strong></li>
</ul> </ul>

View file

@ -195,7 +195,7 @@ def proxy(base=None, local='X-Forwarded-Host', remote='X-Forwarded-For',
if not base: if not base:
base = request.headers.get('Host', '127.0.0.1') base = request.headers.get('Host', '127.0.0.1')
port = request.local.port port = request.local.port
if port != 80: if port != 80 and not base.endswith(':%s' % port):
base += ':%s' % port base += ':%s' % port
if base.find("://") == -1: if base.find("://") == -1:

View file

@ -32,7 +32,7 @@ HASH_FUNCTION = 'sha256' # Must be in hashlib.
# Linear to the hashing time. Adjust to be high but take a reasonable # Linear to the hashing time. Adjust to be high but take a reasonable
# amount of time on your server. Measure with: # amount of time on your server. Measure with:
# python -m timeit -s 'import passwords as p' 'p.make_hash("something")' # python -m timeit -s 'import passwords as p' 'p.make_hash("something")'
COST_FACTOR = 29000 COST_FACTOR = 10000
def make_hash(password): def make_hash(password):

View file

@ -72,7 +72,7 @@ def pbkdf2_bin(data, salt, iterations=1000, keylen=24, hashfunc=None):
rv = u = _pseudorandom(salt + _pack_int(block)) rv = u = _pseudorandom(salt + _pack_int(block))
for i in xrange(iterations - 1): for i in xrange(iterations - 1):
u = _pseudorandom(''.join(map(chr, u))) u = _pseudorandom(''.join(map(chr, u)))
rv = starmap(xor, izip(rv, u)) rv = list(starmap(xor, izip(rv, u)))
buf.extend(rv) buf.extend(rv)
return ''.join(map(chr, buf))[:keylen] return ''.join(map(chr, buf))[:keylen]

View file

@ -220,25 +220,29 @@ class ActivityProcessor(object):
result = self.db.select(query=query, args=args) result = self.db.select(query=query, args=args)
new_session = prev_session = last_id = None
if len(result) > 1:
new_session = {'id': result[0]['id'], new_session = {'id': result[0]['id'],
'rating_key': result[0]['rating_key'], 'rating_key': result[0]['rating_key'],
'view_offset': result[0]['view_offset'], 'view_offset': result[0]['view_offset'],
'user_id': result[0]['user_id'], 'user_id': result[0]['user_id'],
'reference_id': result[0]['reference_id']} 'reference_id': result[0]['reference_id']}
if len(result) == 1:
prev_session = None
else:
prev_session = {'id': result[1]['id'], prev_session = {'id': result[1]['id'],
'rating_key': result[1]['rating_key'], 'rating_key': result[1]['rating_key'],
'view_offset': result[1]['view_offset'], 'view_offset': result[1]['view_offset'],
'user_id': result[1]['user_id'], 'user_id': result[1]['user_id'],
'reference_id': result[1]['reference_id']} 'reference_id': result[1]['reference_id']}
else:
# Get the last insert row id
result = self.db.select(query='SELECT last_insert_rowid() AS last_id')
last_id = result[0]['last_id'] if result else None
query = 'UPDATE session_history SET reference_id = ? WHERE id = ? ' query = 'UPDATE session_history SET reference_id = ? WHERE id = ? '
# If rating_key is the same in the previous session, then set the reference_id to the previous row, else set the reference_id to the new id # If rating_key is the same in the previous session, then set the reference_id to the previous row, else set the reference_id to the new id
if (prev_session is not None) and (prev_session['rating_key'] == new_session['rating_key'] \ if prev_session == new_session == None:
and prev_session['view_offset'] <= new_session['view_offset']): args = [last_id, last_id]
elif prev_session['rating_key'] == new_session['rating_key'] and prev_session['view_offset'] <= new_session['view_offset']:
args = [prev_session['reference_id'], new_session['id']] args = [prev_session['reference_id'], new_session['id']]
else: else:
args = [new_session['id'], new_session['id']] args = [new_session['id'], new_session['id']]

View file

@ -198,7 +198,7 @@ class DataFactory(object):
top_tv = [] top_tv = []
try: try:
query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \ query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \
't.media_type, t.content_rating, t.labels, ' \ 't.media_type, t.content_rating, t.labels, t.started, ' \
'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \ 'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \
'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \ 'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \
' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \ ' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \
@ -210,7 +210,7 @@ class DataFactory(object):
' AND session_history.media_type = "episode" ' \ ' AND session_history.media_type = "episode" ' \
' GROUP BY %s) AS t ' \ ' GROUP BY %s) AS t ' \
'GROUP BY t.grandparent_title ' \ 'GROUP BY t.grandparent_title ' \
'ORDER BY %s DESC ' \ 'ORDER BY %s DESC, started DESC ' \
'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except Exception as e: except Exception as e:
@ -246,7 +246,7 @@ class DataFactory(object):
popular_tv = [] popular_tv = []
try: try:
query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \ query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \
't.media_type, t.content_rating, t.labels, ' \ 't.media_type, t.content_rating, t.labels, t.started, ' \
'COUNT(DISTINCT t.user_id) AS users_watched, ' \ 'COUNT(DISTINCT t.user_id) AS users_watched, ' \
'MAX(t.started) AS last_watch, COUNT(t.id) as total_plays, SUM(t.d) AS total_duration ' \ 'MAX(t.started) AS last_watch, COUNT(t.id) as total_plays, SUM(t.d) AS total_duration ' \
'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \ 'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \
@ -259,7 +259,7 @@ class DataFactory(object):
' AND session_history.media_type = "episode" ' \ ' AND session_history.media_type = "episode" ' \
' GROUP BY %s) AS t ' \ ' GROUP BY %s) AS t ' \
'GROUP BY t.grandparent_title ' \ 'GROUP BY t.grandparent_title ' \
'ORDER BY users_watched DESC, %s DESC ' \ 'ORDER BY users_watched DESC, %s DESC, started DESC ' \
'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except Exception as e: except Exception as e:
@ -293,7 +293,7 @@ class DataFactory(object):
top_movies = [] top_movies = []
try: try:
query = 'SELECT t.id, t.full_title, t.rating_key, t.thumb, t.section_id, ' \ query = 'SELECT t.id, t.full_title, t.rating_key, t.thumb, t.section_id, ' \
't.media_type, t.content_rating, t.labels, ' \ 't.media_type, t.content_rating, t.labels, t.started, ' \
'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \ 'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \
'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \ 'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \
' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \ ' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \
@ -305,7 +305,7 @@ class DataFactory(object):
' AND session_history.media_type = "movie" ' \ ' AND session_history.media_type = "movie" ' \
' GROUP BY %s) AS t ' \ ' GROUP BY %s) AS t ' \
'GROUP BY t.full_title ' \ 'GROUP BY t.full_title ' \
'ORDER BY %s DESC ' \ 'ORDER BY %s DESC, started DESC ' \
'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except Exception as e: except Exception as e:
@ -341,7 +341,7 @@ class DataFactory(object):
popular_movies = [] popular_movies = []
try: try:
query = 'SELECT t.id, t.full_title, t.rating_key, t.thumb, t.section_id, ' \ query = 'SELECT t.id, t.full_title, t.rating_key, t.thumb, t.section_id, ' \
't.media_type, t.content_rating, t.labels, ' \ 't.media_type, t.content_rating, t.labels, t.started, ' \
'COUNT(DISTINCT t.user_id) AS users_watched, ' \ 'COUNT(DISTINCT t.user_id) AS users_watched, ' \
'MAX(t.started) AS last_watch, COUNT(t.id) as total_plays, SUM(t.d) AS total_duration ' \ 'MAX(t.started) AS last_watch, COUNT(t.id) as total_plays, SUM(t.d) AS total_duration ' \
'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \ 'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \
@ -354,7 +354,7 @@ class DataFactory(object):
' AND session_history.media_type = "movie" ' \ ' AND session_history.media_type = "movie" ' \
' GROUP BY %s) AS t ' \ ' GROUP BY %s) AS t ' \
'GROUP BY t.full_title ' \ 'GROUP BY t.full_title ' \
'ORDER BY users_watched DESC, %s DESC ' \ 'ORDER BY users_watched DESC, %s DESC, started DESC ' \
'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except Exception as e: except Exception as e:
@ -388,7 +388,7 @@ class DataFactory(object):
top_music = [] top_music = []
try: try:
query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \ query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \
't.media_type, t.content_rating, t.labels, ' \ 't.media_type, t.content_rating, t.labels, t.started, ' \
'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \ 'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \
'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \ 'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \
' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \ ' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \
@ -400,7 +400,7 @@ class DataFactory(object):
' AND session_history.media_type = "track" ' \ ' AND session_history.media_type = "track" ' \
' GROUP BY %s) AS t ' \ ' GROUP BY %s) AS t ' \
'GROUP BY t.grandparent_title ' \ 'GROUP BY t.grandparent_title ' \
'ORDER BY %s DESC ' \ 'ORDER BY %s DESC, started DESC ' \
'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except Exception as e: except Exception as e:
@ -436,7 +436,7 @@ class DataFactory(object):
popular_music = [] popular_music = []
try: try:
query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \ query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \
't.media_type, t.content_rating, t.labels, ' \ 't.media_type, t.content_rating, t.labels, t.started, ' \
'COUNT(DISTINCT t.user_id) AS users_watched, ' \ 'COUNT(DISTINCT t.user_id) AS users_watched, ' \
'MAX(t.started) AS last_watch, COUNT(t.id) as total_plays, SUM(t.d) AS total_duration ' \ 'MAX(t.started) AS last_watch, COUNT(t.id) as total_plays, SUM(t.d) AS total_duration ' \
'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \ 'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \
@ -449,7 +449,7 @@ class DataFactory(object):
' AND session_history.media_type = "track" ' \ ' AND session_history.media_type = "track" ' \
' GROUP BY %s) AS t ' \ ' GROUP BY %s) AS t ' \
'GROUP BY t.grandparent_title ' \ 'GROUP BY t.grandparent_title ' \
'ORDER BY users_watched DESC, %s DESC ' \ 'ORDER BY users_watched DESC, %s DESC, started DESC ' \
'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except Exception as e: except Exception as e:
@ -482,7 +482,7 @@ class DataFactory(object):
elif stat == 'top_users': elif stat == 'top_users':
top_users = [] top_users = []
try: try:
query = 'SELECT t.user, t.user_id, t.user_thumb, t.custom_thumb, ' \ query = 'SELECT t.user, t.user_id, t.user_thumb, t.custom_thumb, t.started, ' \
'(CASE WHEN t.friendly_name IS NULL THEN t.username ELSE t.friendly_name END) ' \ '(CASE WHEN t.friendly_name IS NULL THEN t.username ELSE t.friendly_name END) ' \
' AS friendly_name, ' \ ' AS friendly_name, ' \
'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \ 'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \
@ -496,7 +496,7 @@ class DataFactory(object):
' >= datetime("now", "-%s days", "localtime") ' \ ' >= datetime("now", "-%s days", "localtime") ' \
' GROUP BY %s) AS t ' \ ' GROUP BY %s) AS t ' \
'GROUP BY t.user_id ' \ 'GROUP BY t.user_id ' \
'ORDER BY %s DESC ' \ 'ORDER BY %s DESC, started DESC ' \
'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except Exception as e: except Exception as e:
@ -536,7 +536,7 @@ class DataFactory(object):
top_platform = [] top_platform = []
try: try:
query = 'SELECT t.platform, ' \ query = 'SELECT t.platform, t.started, ' \
'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \ 'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \
'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \ 'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \
' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \ ' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \
@ -547,7 +547,7 @@ class DataFactory(object):
' >= datetime("now", "-%s days", "localtime") ' \ ' >= datetime("now", "-%s days", "localtime") ' \
' GROUP BY %s) AS t ' \ ' GROUP BY %s) AS t ' \
'GROUP BY t.platform ' \ 'GROUP BY t.platform ' \
'ORDER BY %s DESC ' \ 'ORDER BY %s DESC, started DESC ' \
'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except Exception as e: except Exception as e:

View file

@ -786,12 +786,13 @@ class XBMC(object):
raise Exception raise Exception
else: else:
logger.info(u"PlexPy Notifiers :: XBMC notification sent.") logger.info(u"PlexPy Notifiers :: XBMC notification sent.")
return True
except Exception: except Exception:
logger.warn(u"PlexPy Notifiers :: XBMC notification filed.") logger.warn(u"PlexPy Notifiers :: XBMC notification failed.")
return False return False
return True
def return_config_options(self): def return_config_options(self):
config_option = [{'label': 'XBMC Host:Port', config_option = [{'label': 'XBMC Host:Port',
'value': self.hosts, 'value': self.hosts,
@ -870,12 +871,13 @@ class Plex(object):
raise Exception raise Exception
else: else:
logger.info(u"PlexPy Notifiers :: Plex Home Theater notification sent.") logger.info(u"PlexPy Notifiers :: Plex Home Theater notification sent.")
return True
except Exception: except Exception:
logger.warn(u"PlexPy Notifiers :: Plex Home Theater notification filed.") logger.warn(u"PlexPy Notifiers :: Plex Home Theater notification failed.")
return False return False
return True
def return_config_options(self): def return_config_options(self):
config_option = [{'label': 'Plex Home Theater Host:Port', config_option = [{'label': 'Plex Home Theater Host:Port',
'value': self.client_hosts, 'value': self.client_hosts,
@ -2567,18 +2569,12 @@ class JOIN(object):
'title': subject.encode("utf-8"), 'title': subject.encode("utf-8"),
'text': message.encode("utf-8")} 'text': message.encode("utf-8")}
http_handler = HTTPSConnection("joinjoaomgcd.appspot.com") response = requests.post('https://joinjoaomgcd.appspot.com/_ah/api/messaging/v1/sendPush',
http_handler.request("POST", params=data)
"/_ah/api/messaging/v1/sendPush?%s" % urlencode(data)) request_status = response.status_code
response = http_handler.getresponse()
request_status = response.status
# logger.debug(u"PushBullet response status: %r" % request_status)
# logger.debug(u"PushBullet response headers: %r" % response.getheaders())
# logger.debug(u"PushBullet response body: %r" % response.read())
if request_status == 200: if request_status == 200:
data = json.loads(response.read()) data = json.loads(response.text)
if data.get('success'): if data.get('success'):
logger.info(u"PlexPy Notifiers :: Join notification sent.") logger.info(u"PlexPy Notifiers :: Join notification sent.")
return True return True
@ -2632,7 +2628,10 @@ class JOIN(object):
return {'': ''} return {'': ''}
def return_config_options(self): def return_config_options(self):
devices = '<br>'.join(['%s: %s' % (v, k) for k, v in self.get_devices().iteritems() if k]) devices = '<br>'.join(['%s: <span class="inline-pre">%s</span>'
% (v, k) for k, v in self.get_devices().iteritems() if k])
if not devices:
devices = 'Enter your Join API key to load your device list.'
config_option = [{'label': 'Join API Key', config_option = [{'label': 'Join API Key',
'value': self.apikey, 'value': self.apikey,

View file

@ -179,7 +179,7 @@ class PmsConnect(object):
return request return request
def get_recently_added(self, count='0', output_format=''): def get_recently_added(self, start='0', count='0', output_format=''):
""" """
Return list of recently added items. Return list of recently added items.
@ -188,7 +188,7 @@ class PmsConnect(object):
Output: array Output: array
""" """
uri = '/library/recentlyAdded?X-Plex-Container-Start=0&X-Plex-Container-Size=' + count uri = '/library/recentlyAdded?X-Plex-Container-Start=%s&X-Plex-Container-Size=%s' % (start, count)
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol, proto=self.protocol,
request_type='GET', request_type='GET',
@ -196,7 +196,7 @@ class PmsConnect(object):
return request return request
def get_library_recently_added(self, section_id='', count='0', output_format=''): def get_library_recently_added(self, section_id='', start='0', count='0', output_format=''):
""" """
Return list of recently added items. Return list of recently added items.
@ -205,7 +205,7 @@ class PmsConnect(object):
Output: array Output: array
""" """
uri = '/library/sections/' + section_id + '/recentlyAdded?X-Plex-Container-Start=0&X-Plex-Container-Size=' + count 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, request = self.request_handler.make_request(uri=uri,
proto=self.protocol, proto=self.protocol,
request_type='GET', request_type='GET',
@ -458,7 +458,7 @@ class PmsConnect(object):
return request return request
def get_recently_added_details(self, section_id='', count='0'): def get_recently_added_details(self, section_id='', start='0', count='0'):
""" """
Return processed and validated list of recently added items. Return processed and validated list of recently added items.
@ -467,9 +467,9 @@ class PmsConnect(object):
Output: array Output: array
""" """
if section_id: if section_id:
recent = self.get_library_recently_added(section_id, count, output_format='xml') recent = self.get_library_recently_added(section_id, start, count, output_format='xml')
else: else:
recent = self.get_recently_added(count, output_format='xml') recent = self.get_recently_added(start, count, output_format='xml')
try: try:
xml_head = recent.getElementsByTagName('MediaContainer') xml_head = recent.getElementsByTagName('MediaContainer')

View file

@ -1,2 +1,2 @@
PLEXPY_VERSION = "master" PLEXPY_VERSION = "master"
PLEXPY_RELEASE_VERSION = "1.4.0" PLEXPY_RELEASE_VERSION = "1.4.1"

View file

@ -36,7 +36,7 @@ from plexpy.plextv import PlexTV
SESSION_KEY = '_cp_username' SESSION_KEY = '_cp_username'
def user_login(username=None, password=None): def user_login(username=None, password=None):
if not username and not password: if not username or not password:
return None return None
# Try to login to Plex.tv to check if the user has a vaild account # Try to login to Plex.tv to check if the user has a vaild account
@ -119,7 +119,7 @@ def check_auth(*args, **kwargs):
if not condition(): if not condition():
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT) raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT)
else: else:
raise cherrypy.HTTPRedirect("auth/logout") raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "auth/logout")
def requireAuth(*conditions): def requireAuth(*conditions):
"""A decorator that appends conditions to the auth.require config """A decorator that appends conditions to the auth.require config
@ -204,14 +204,14 @@ class AuthController(object):
@cherrypy.expose @cherrypy.expose
def index(self): def index(self):
raise cherrypy.HTTPRedirect("login") raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "auth/login")
@cherrypy.expose @cherrypy.expose
def login(self, username=None, password=None, remember_me='0', admin_login='0'): def login(self, username=None, password=None, remember_me='0', admin_login='0'):
if not cherrypy.config.get('tools.sessions.on'): if not cherrypy.config.get('tools.sessions.on'):
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT) raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT)
if username is None or password is None: if not username and not password:
return self.get_loginform() return self.get_loginform()
(vaild_login, user_group) = check_credentials(username, password, admin_login) (vaild_login, user_group) = check_credentials(username, password, admin_login)
@ -257,4 +257,4 @@ class AuthController(object):
if _session and _session['user']: if _session and _session['user']:
cherrypy.request.login = None cherrypy.request.login = None
self.on_logout(_session['user'], _session['user_group']) self.on_logout(_session['user'], _session['user_group'])
raise cherrypy.HTTPRedirect("login") raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "auth/login")

View file

@ -82,9 +82,9 @@ class WebInterface(object):
@requireAuth() @requireAuth()
def index(self): def index(self):
if plexpy.CONFIG.FIRST_RUN_COMPLETE: if plexpy.CONFIG.FIRST_RUN_COMPLETE:
raise cherrypy.HTTPRedirect("home") raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "home")
else: else:
raise cherrypy.HTTPRedirect("welcome") raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "welcome")
##### Welcome ##### ##### Welcome #####
@ -118,7 +118,7 @@ class WebInterface(object):
# The setup wizard just refreshes the page on submit so we must redirect to home if config set. # The setup wizard just refreshes the page on submit so we must redirect to home if config set.
if plexpy.CONFIG.FIRST_RUN_COMPLETE: if plexpy.CONFIG.FIRST_RUN_COMPLETE:
plexpy.initialize_scheduler() plexpy.initialize_scheduler()
raise cherrypy.HTTPRedirect("home") raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "home")
else: else:
return serve_template(templatename="welcome.html", title="Welcome", config=config) return serve_template(templatename="welcome.html", title="Welcome", config=config)
@ -1987,24 +1987,14 @@ class WebInterface(object):
def getLog(self, start=0, length=100, **kwargs): def getLog(self, start=0, length=100, **kwargs):
start = int(start) start = int(start)
length = int(length) length = int(length)
search_value = ""
search_regex = ""
order_column = 0
order_dir = "desc"
if 'order[0][dir]' in kwargs:
order_dir = kwargs.get('order[0][dir]', "desc") order_dir = kwargs.get('order[0][dir]', "desc")
if 'order[0][column]' in kwargs:
order_column = kwargs.get('order[0][column]', "0") order_column = kwargs.get('order[0][column]', "0")
if 'search[value]' in kwargs:
search_value = kwargs.get('search[value]', "") search_value = kwargs.get('search[value]', "")
search_regex = kwargs.get('search[regex]', "") # Remove?
if 'search[regex]' in kwargs: sortcolumn = 0
search_regex = kwargs.get('search[regex]', "")
filt = [] filt = []
filtered = []
fa = filt.append fa = filt.append
with open(os.path.join(plexpy.CONFIG.LOG_DIR, logger.FILENAME)) as f: with open(os.path.join(plexpy.CONFIG.LOG_DIR, logger.FILENAME)) as f:
for l in f.readlines(): for l in f.readlines():
@ -2017,22 +2007,24 @@ class WebInterface(object):
# Add traceback message to previous msg. # Add traceback message to previous msg.
tl = (len(filt) - 1) tl = (len(filt) - 1)
n = len(l) - len(l.lstrip(' ')) n = len(l) - len(l.lstrip(' '))
l = '&nbsp;' * (2*n) + l[n:] l = '&nbsp;' * (2 * n) + l[n:]
filt[tl][2] += '<br>' + l filt[tl][2] += '<br>' + l
continue continue
filtered = []
if search_value == '': if search_value == '':
filtered = filt filtered = filt
else: else:
filtered = [row for row in filt for column in row if search_value.lower() in column.lower()] filtered = [row for row in filt for column in row if search_value.lower() in column.lower()]
sortcolumn = 0
if order_column == '1': if order_column == '1':
sortcolumn = 2 sortcolumn = 2
elif order_column == '2': elif order_column == '2':
sortcolumn = 1 sortcolumn = 1
filtered.sort(key=lambda x: x[sortcolumn], reverse=order_dir == "desc")
filtered.sort(key=lambda x: x[sortcolumn])
if order_dir == 'desc':
filtered = filtered[::-1]
rows = filtered[start:(start + length)] rows = filtered[start:(start + length)]
@ -2215,7 +2207,7 @@ class WebInterface(object):
log_dir=plexpy.CONFIG.LOG_DIR, verbose=plexpy.VERBOSE) log_dir=plexpy.CONFIG.LOG_DIR, verbose=plexpy.VERBOSE)
logger.info(u"Verbose toggled, set to %s", plexpy.VERBOSE) logger.info(u"Verbose toggled, set to %s", plexpy.VERBOSE)
logger.debug(u"If you read this message, debug logging is available") logger.debug(u"If you read this message, debug logging is available")
raise cherrypy.HTTPRedirect("logs") raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "logs")
@cherrypy.expose @cherrypy.expose
@requireAuth() @requireAuth()
@ -2262,6 +2254,7 @@ class WebInterface(object):
"http_port": plexpy.CONFIG.HTTP_PORT, "http_port": plexpy.CONFIG.HTTP_PORT,
"http_password": http_password, "http_password": http_password,
"http_root": plexpy.CONFIG.HTTP_ROOT, "http_root": plexpy.CONFIG.HTTP_ROOT,
"http_proxy": checked(plexpy.CONFIG.HTTP_PROXY),
"launch_browser": checked(plexpy.CONFIG.LAUNCH_BROWSER), "launch_browser": checked(plexpy.CONFIG.LAUNCH_BROWSER),
"enable_https": checked(plexpy.CONFIG.ENABLE_HTTPS), "enable_https": checked(plexpy.CONFIG.ENABLE_HTTPS),
"https_create_cert": checked(plexpy.CONFIG.HTTPS_CREATE_CERT), "https_create_cert": checked(plexpy.CONFIG.HTTPS_CREATE_CERT),
@ -2374,7 +2367,7 @@ class WebInterface(object):
"ip_logging_enable", "movie_logging_enable", "tv_logging_enable", "music_logging_enable", "ip_logging_enable", "movie_logging_enable", "tv_logging_enable", "music_logging_enable",
"notify_consecutive", "notify_upload_posters", "notify_recently_added", "notify_recently_added_grandparent", "notify_consecutive", "notify_upload_posters", "notify_recently_added", "notify_recently_added_grandparent",
"monitor_pms_updates", "monitor_remote_access", "get_file_sizes", "log_blacklist", "http_hash_password", "monitor_pms_updates", "monitor_remote_access", "get_file_sizes", "log_blacklist", "http_hash_password",
"allow_guest_access", "cache_images" "allow_guest_access", "cache_images", "http_proxy"
] ]
for checked_config in checked_configs: for checked_config in checked_configs:
if checked_config not in kwargs: if checked_config not in kwargs:
@ -2857,7 +2850,7 @@ class WebInterface(object):
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
def checkGithub(self): def checkGithub(self):
versioncheck.checkGithub() versioncheck.checkGithub()
raise cherrypy.HTTPRedirect("home") raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "home")
@cherrypy.expose @cherrypy.expose
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
@ -3336,7 +3329,7 @@ class WebInterface(object):
@cherrypy.tools.json_out() @cherrypy.tools.json_out()
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
@addtoapi("get_recently_added") @addtoapi("get_recently_added")
def get_recently_added_details(self, count='0', section_id='', **kwargs): def get_recently_added_details(self, start='0', count='0', section_id='', **kwargs):
""" Get all items that where recelty added to plex. """ Get all items that where recelty added to plex.
``` ```
@ -3344,6 +3337,7 @@ class WebInterface(object):
count (str): Number of items to return count (str): Number of items to return
Optional parameters: Optional parameters:
start (str): The item number to start at
section_id (str): The id of the Plex library section section_id (str): The id of the Plex library section
Returns: Returns:
@ -3373,7 +3367,7 @@ class WebInterface(object):
``` ```
""" """
pms_connect = pmsconnect.PmsConnect() pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_recently_added_details(count=count, section_id=section_id) result = pms_connect.get_recently_added_details(start=start, count=count, section_id=section_id)
if result: if result:
return result return result
@ -3618,13 +3612,13 @@ class WebInterface(object):
pms_connect = pmsconnect.PmsConnect(token=plexpy.CONFIG.PMS_TOKEN) pms_connect = pmsconnect.PmsConnect(token=plexpy.CONFIG.PMS_TOKEN)
result = pms_connect.get_current_activity() result = pms_connect.get_current_activity()
if result:
data_factory = datafactory.DataFactory() data_factory = datafactory.DataFactory()
for session in result['sessions']: for session in result['sessions']:
if not session['ip_address']: if not session['ip_address']:
ip_address = data_factory.get_session_ip(session['session_key']) ip_address = data_factory.get_session_ip(session['session_key'])
session['ip_address'] = ip_address session['ip_address'] = ip_address
if result:
return result return result
else: else:
logger.warn(u"Unable to retrieve data for get_activity.") logger.warn(u"Unable to retrieve data for get_activity.")