Merge branch 'dev'

This commit is contained in:
Tim 2015-09-20 14:16:10 +02:00
commit 1761b14fe9
17 changed files with 648 additions and 161 deletions

View file

@ -1,5 +1,16 @@
# Changelog # Changelog
## v1.1.10 (2015-09-20)
* Added dedicated settings section for home stats configuration with ability to show/hide selected stats and sections.
* Added support for Twitter notifications.
* Only show music in graphs if music logging is enabled.
* The monitoring ignore interval now excludes paused time.
* Fix display bug on activity panel which incorrectly reported transcoding sometimes.
* Fix bug with Email notification TLS checkbox when it would be disabled by changing any other settings afterwards.
* Fix issue on some Python releases where the webbrowser library isn't included.
## v1.1.9 (2015-09-14) ## v1.1.9 (2015-09-14)
* Another JonnyWong release. I'm going to stop thanking you now ;) * Another JonnyWong release. I'm going to stop thanking you now ;)

View file

@ -33,6 +33,33 @@ select.input-sm {
color: #999; color: #999;
outline: none; outline: none;
} }
select[multiple] {
height: 125px;
margin: 5px 0 5px 0;
color: #fff;
border: 0px solid #444;
background: #555;
padding: 2px 2px;
background-color: #555;
border-radius: 3px;
transition: background-color .3s;
}
select[multiple]:focus {
outline: 0;
outline: thin dotted \9;
color: #555;
background-color: #fff;
transition: background-color .3s;
}
select[multiple]:focus::-webkit-scrollbar-thumb {
background-color: rgba(0,0,0,.15);
}
select[multiple] option {
padding: 6px 10px;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
}
img { img {
-webkit-box-sizing: content-box; -webkit-box-sizing: content-box;
-moz-box-sizing: content-box; -moz-box-sizing: content-box;

View file

@ -119,6 +119,8 @@ DOCUMENTATION :: END
% if a['type'] == 'track': % if a['type'] == 'track':
% if a['audio_decision'] == 'direct play': % if a['audio_decision'] == 'direct play':
Stream &nbsp;<strong>Direct Play</strong> Stream &nbsp;<strong>Direct Play</strong>
% elif a['audio_decision'] == 'copy':
Stream &nbsp;<strong>Direct Stream</strong>
% else: % else:
Stream &nbsp;<strong>Transcoding Stream &nbsp;<strong>Transcoding
(Speed: ${a['transcode_speed']}) (Speed: ${a['transcode_speed']})
@ -136,8 +138,10 @@ DOCUMENTATION :: END
Audio &nbsp;<strong>Transcode (${a['transcode_audio_codec']}) (${a['transcode_audio_channels']}ch)</strong> Audio &nbsp;<strong>Transcode (${a['transcode_audio_codec']}) (${a['transcode_audio_channels']}ch)</strong>
% endif % endif
% elif a['type'] == 'episode' or a['type'] == 'movie' or a['type'] == 'clip': % elif a['type'] == 'episode' or a['type'] == 'movie' or a['type'] == 'clip':
% if a['video_decision'] == 'direct play': % if a['video_decision'] == 'direct play' and a['audio_decision'] == 'direct play':
Stream &nbsp;<strong>Direct Play</strong> Stream &nbsp;<strong>Direct Play</strong>
% elif a['video_decision'] == 'copy' and a['audio_decision'] == 'copy':
Stream &nbsp;<strong>Direct Stream</strong>
% else: % else:
Stream &nbsp;<strong>Transcoding Stream &nbsp;<strong>Transcoding
(Speed: ${a['transcode_speed']}) (Speed: ${a['transcode_speed']})
@ -165,6 +169,8 @@ DOCUMENTATION :: END
% elif a['type'] == 'photo': % elif a['type'] == 'photo':
% if a['video_decision'] == 'direct play': % if a['video_decision'] == 'direct play':
Stream &nbsp;<strong>Direct Play</strong> Stream &nbsp;<strong>Direct Play</strong>
% elif a['video_decision'] == 'copy':
Stream &nbsp;<strong>Direct Stream</strong>
% else: % else:
Stream &nbsp;<strong> Stream &nbsp;<strong>
Transcoding Transcoding

View file

@ -294,6 +294,8 @@
$('a[data-toggle=tab][href=' + current_tab + ']').trigger('click'); $('a[data-toggle=tab][href=' + current_tab + ']').trigger('click');
} }
var music_visible = (${config['music_logging_enable']} == 1 ? true : false);
function loadGraphsTab1(time_range, yaxis) { function loadGraphsTab1(time_range, yaxis) {
setGraphFormat(yaxis); setGraphFormat(yaxis);
@ -319,6 +321,7 @@
hc_plays_by_day_options.yAxis.min = 0; hc_plays_by_day_options.yAxis.min = 0;
hc_plays_by_day_options.xAxis.categories = dateArray; hc_plays_by_day_options.xAxis.categories = dateArray;
hc_plays_by_day_options.series = data.series; hc_plays_by_day_options.series = data.series;
hc_plays_by_day_options.series[2].visible = music_visible;
var hc_plays_by_day = new Highcharts.Chart(hc_plays_by_day_options); var hc_plays_by_day = new Highcharts.Chart(hc_plays_by_day_options);
} }
}); });
@ -331,6 +334,7 @@
success: function(data) { success: function(data) {
hc_plays_by_dayofweek_options.xAxis.categories = data.categories; hc_plays_by_dayofweek_options.xAxis.categories = data.categories;
hc_plays_by_dayofweek_options.series = data.series; hc_plays_by_dayofweek_options.series = data.series;
hc_plays_by_dayofweek_options.series[2].visible = music_visible;
var hc_plays_by_dayofweek = new Highcharts.Chart(hc_plays_by_dayofweek_options); var hc_plays_by_dayofweek = new Highcharts.Chart(hc_plays_by_dayofweek_options);
} }
}); });
@ -343,6 +347,7 @@
success: function(data) { success: function(data) {
hc_plays_by_hourofday_options.xAxis.categories = data.categories; hc_plays_by_hourofday_options.xAxis.categories = data.categories;
hc_plays_by_hourofday_options.series = data.series; hc_plays_by_hourofday_options.series = data.series;
hc_plays_by_hourofday_options.series[2].visible = music_visible;
var hc_plays_by_hourofday = new Highcharts.Chart(hc_plays_by_hourofday_options); var hc_plays_by_hourofday = new Highcharts.Chart(hc_plays_by_hourofday_options);
} }
}); });
@ -355,6 +360,7 @@
success: function(data) { success: function(data) {
hc_plays_by_platform_options.xAxis.categories = data.categories; hc_plays_by_platform_options.xAxis.categories = data.categories;
hc_plays_by_platform_options.series = data.series; hc_plays_by_platform_options.series = data.series;
hc_plays_by_platform_options.series[2].visible = music_visible;
var hc_plays_by_platform = new Highcharts.Chart(hc_plays_by_platform_options); var hc_plays_by_platform = new Highcharts.Chart(hc_plays_by_platform_options);
} }
}); });
@ -367,6 +373,7 @@
success: function(data) { success: function(data) {
hc_plays_by_user_options.xAxis.categories = data.categories; hc_plays_by_user_options.xAxis.categories = data.categories;
hc_plays_by_user_options.series = data.series; hc_plays_by_user_options.series = data.series;
hc_plays_by_user_options.series[2].visible = music_visible;
var hc_plays_by_user = new Highcharts.Chart(hc_plays_by_user_options); var hc_plays_by_user = new Highcharts.Chart(hc_plays_by_user_options);
} }
}); });
@ -462,6 +469,7 @@
hc_plays_by_month_options.yAxis.min = 0; hc_plays_by_month_options.yAxis.min = 0;
hc_plays_by_month_options.xAxis.categories = data.categories; hc_plays_by_month_options.xAxis.categories = data.categories;
hc_plays_by_month_options.series = data.series; hc_plays_by_month_options.series = data.series;
hc_plays_by_month_options.series[2].visible = music_visible;
var hc_plays_by_month = new Highcharts.Chart(hc_plays_by_month_options); var hc_plays_by_month = new Highcharts.Chart(hc_plays_by_month_options);
} }
}); });

View file

@ -17,19 +17,19 @@ data[array_index]['rows'] :: Usable parameters
row_id Return the db row id for a metadata item if one exists row_id Return the db row id for a metadata item if one exists
== Only if 'stat_id' is 'top_tv' or 'popular_tv' or 'top_movies' or 'popular_movies' or 'last_watched' == == Only if 'stat_id' is 'top_tv' or 'popular_tv' or 'top_movies' or 'popular_movies' or 'top_music' or 'popular_music' or 'last_watched' ==
thumb Return the thumb for the media item. thumb Return the thumb for the media item.
== Only if 'stat_id' is 'top_tv' or 'popular_tv' == == Only if 'stat_id' is 'top_tv' or 'popular_tv' or 'top_music' or 'popular_music' ==
grandparent_thumb Returns location of the item's thumbnail. Use with pms_image_proxy. grandparent_thumb Returns location of the item's thumbnail. Use with pms_image_proxy.
rating_key Returns the unique identifier for the media item. rating_key Returns the unique identifier for the media item.
title Returns the title for the associated stat. title Returns the title for the associated stat.
== Only if 'stat_id' is 'top_tv' or 'top_movies' or 'top_user' or 'top_platform' == == Only if 'stat_id' is 'top_tv' or 'top_movies' or 'top_music' or 'top_user' or 'top_platform' ==
total_plays Returns the count for the associated stat. total_plays Returns the count for the associated stat.
total_duration Returns the total duration for the associated stat. total_duration Returns the total duration for the associated stat.
== Only of 'stat_id' is 'popular_tv' or 'popular_movies' == == Only of 'stat_id' is 'popular_tv' or 'popular_movies' or 'popular_music' ==
users_watched Returns the count for the associated stat. users_watched Returns the count for the associated stat.
== Only if 'stat_id' is 'top_user' or 'last_watched' == == Only if 'stat_id' is 'top_user' or 'last_watched' ==
@ -372,6 +372,158 @@ DOCUMENTATION :: END
% endif % endif
</li> </li>
</div> </div>
% elif top_stat['stat_id'] == 'top_music' and top_stat['rows']:
<div class="home-platforms-instance">
<li>
<div class="home-platforms-instance-info">
<div class="home-platforms-instance-name">
<h4>Most Listened to Artist</h4>
</div>
<div class="home-platforms-instance-playcount">
<h4>
<a href="info?item_id=${top_stat['rows'][0]['rating_key']}">
${top_stat['rows'][0]['title']}
</a>
</h4>
% if top_stat['stat_type'] == 'total_plays':
<h3>${top_stat['rows'][0]['total_plays']}</h3>
<p> plays</p>
% else:
${top_stat['rows'][0]['total_duration'] | hd}
% endif
</div>
</div>
<a href="info?item_id=${top_stat['rows'][0]['rating_key']}">
% if top_stat['rows'][0]['grandparent_thumb']:
<div class="home-platforms-instance-poster">
<div class="home-platforms-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][0]['grandparent_thumb']}&width=300&height=300&fallback=poster);"></div>
</div>
% else:
<div class="home-platforms-instance-poster">
<div class="home-platforms-poster-face" style="background-image: url(interfaces/default/images/poster.png);"></div>
</div>
% endif
</a>
%if len(top_stat['rows']) > 1:
<div class="home-platforms-instance-list-chevron"><i class="fa fa-chevron-down"></i></div>
<ul class="list-unstyled">
<div class="slider">
<div class="home-platforms-instance-list">
% for row in top_stat['rows']:
%if loop.index > 0:
<li>
<div class="home-platforms-instance-list-info">
<div class="home-platforms-instance-list-name">
<h5>
<a href="info?item_id=${top_stat['rows'][loop.index]['rating_key']}">
${top_stat['rows'][loop.index]['title']}
</a>
</h5>
</div>
<div class="home-platforms-instance-list-playcount">
% if top_stat['stat_type'] == 'total_plays':
<h3>${top_stat['rows'][loop.index]['total_plays']}</h3>
<p> plays</p>
% else:
${top_stat['rows'][loop.index]['total_duration'] | hd}
% endif
</div>
</div>
<a href="info?item_id=${top_stat['rows'][loop.index]['rating_key']}">
% if top_stat['rows'][loop.index]['grandparent_thumb']:
<div class="home-platforms-instance-list-poster">
<div class="home-platforms-list-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][loop.index]['grandparent_thumb']}&width=300&height=300&fallback=poster);"></div>
</div>
% else:
<div class="home-platforms-instance-poster2">
<div class="home-platforms-list-poster-face" style="background-image: url(interfaces/default/images/poster.png);"></div>
</div>
% endif
</a>
<div class="home-platforms-instance-list-number">
<h4>${loop.index + 1}</h4>
</div>
</li>
% endif
% endfor
</div>
</div>
</ul>
% endif
</li>
</div>
% elif top_stat['stat_id'] == 'popular_music' and top_stat['rows']:
<div class="home-platforms-instance">
<li>
<div class="home-platforms-instance-info">
<div class="home-platforms-instance-name">
<h4>Most Popular Artist</h4>
</div>
<div class="home-platforms-instance-playcount">
<h4>
<a href="info?item_id=${top_stat['rows'][0]['rating_key']}">
${top_stat['rows'][0]['title']}
</a>
</h4>
<h3>${top_stat['rows'][0]['users_watched']}</h3>
<p> users</p>
</div>
</div>
<a href="info?item_id=${top_stat['rows'][0]['rating_key']}">
% if top_stat['rows'][0]['grandparent_thumb'] != '':
<div class="home-platforms-instance-poster">
<div class="home-platforms-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][0]['grandparent_thumb']}&width=300&height=300&fallback=poster);"></div>
</div>
% else:
<div class="home-platforms-instance-poster">
<div class="home-platforms-poster-face" style="background-image: url(interfaces/default/images/poster.png);"></div>
</div>
% endif
</a>
%if len(top_stat['rows']) > 1:
<div class="home-platforms-instance-list-chevron"><i class="fa fa-chevron-down"></i></div>
<ul class="list-unstyled">
<div class="slider">
<div class="home-platforms-instance-list">
% for row in top_stat['rows']:
%if loop.index > 0:
<li>
<div class="home-platforms-instance-list-info">
<div class="home-platforms-instance-list-name">
<h5>
<a href="info?item_id=${top_stat['rows'][loop.index]['rating_key']}">
${top_stat['rows'][loop.index]['title']}
</a>
</h5>
</div>
<div class="home-platforms-instance-list-playcount">
<h3>${top_stat['rows'][loop.index]['users_watched']}</h3>
<p> users</p>
</div>
</div>
<a href="info?item_id=${top_stat['rows'][loop.index]['rating_key']}">
% if top_stat['rows'][loop.index]['grandparent_thumb']:
<div class="home-platforms-instance-list-poster">
<div class="home-platforms-list-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][loop.index]['grandparent_thumb']}&width=300&height=300&fallback=poster);"></div>
</div>
% else:
<div class="home-platforms-instance-poster2">
<div class="home-platforms-list-poster-face" style="background-image: url(interfaces/default/images/poster.png);"></div>
</div>
% endif
</a>
<div class="home-platforms-instance-list-number">
<h4>${loop.index + 1}</h4>
</div>
</li>
% endif
% endfor
</div>
</div>
</ul>
% endif
</li>
</div>
% elif top_stat['stat_id'] == 'top_users' and top_stat['rows']: % elif top_stat['stat_id'] == 'top_users' and top_stat['rows']:
<div class="home-platforms-instance"> <div class="home-platforms-instance">
<li> <li>

View file

@ -16,6 +16,7 @@
</div> </div>
</div> </div>
</div> </div>
% if config['home_stats_cards'] > 'watch_statistics':
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div class="padded-header"> <div class="padded-header">
@ -27,6 +28,8 @@
</div> </div>
</div> </div>
</div> </div>
% endif
% if config['home_library_cards'] > 'library_statistics':
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div class="padded-header" id="library-statistics-header"> <div class="padded-header" id="library-statistics-header">
@ -38,6 +41,7 @@
</div> </div>
</div> </div>
</div> </div>
% endif
<div class='row'> <div class='row'>
<div class="col-md-12"> <div class="col-md-12">
<div class="padded-header"> <div class="padded-header">
@ -82,12 +86,12 @@
currentActivity(); currentActivity();
setInterval(currentActivity, 15000); setInterval(currentActivity, 15000);
function getHomeStats(days, stat_type, stat_count) { function getHomeStats(days) {
$.ajax({ $.ajax({
url: 'home_stats', url: 'home_stats',
cache: false, cache: false,
async: true, async: true,
data: {time_range: days, stat_type: stat_type, stat_count: stat_count}, data: { },
complete: function(xhr, status) { complete: function(xhr, status) {
$("#home-stats").html(xhr.responseText); $("#home-stats").html(xhr.responseText);
} }
@ -165,7 +169,7 @@
} }
}); });
getHomeStats(${config['home_stats_length']}, ${config['home_stats_type']}, ${config['home_stats_count']}); getHomeStats();
getLibraryStatsHeader(); getLibraryStatsHeader();
getLibraryStats(); getLibraryStats();

View file

@ -73,6 +73,6 @@ DOCUMENTATION :: END
% endfor % endfor
</ul> </ul>
% else: % else:
<div class="text-muted">Unable to retrieve data from database. Please check your <a href="settings">settings</a>. <div class="text-muted">Unable to retrieve data from server. Please check your <a href="settings">settings</a>.
</div><br> </div><br>
% endif % endif

View file

@ -1,3 +1,6 @@
<%!
from plexpy import helpers
%>
% if data: % if data:
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
@ -9,9 +12,9 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<form action="set_notification_config" method="post" class="form" id="set_notification_config" data-parsley-validate> <form action="set_notification_config" method="post" class="form" id="set_notification_config" data-parsley-validate>
<div class="col-md-6"> <div class="col-md-8">
% for item in data: % for item in data:
% if item['input_type'] != 'checkbox': % if item['input_type'] == 'text' or item['input_type'] == 'number' or item['input_type'] == 'password':
<div class="form-group"> <div class="form-group">
<label for="${item['name']}">${item['label']}</label> <label for="${item['name']}">${item['label']}</label>
<input type="${item['input_type']}" class="form-control" id="${item['name']}" name="${item['name']}" value="${item['value']}" size="30"> <input type="${item['input_type']}" class="form-control" id="${item['name']}" name="${item['name']}" value="${item['value']}" size="30">
@ -20,12 +23,18 @@
% endif % endif
<p class="help-block">${item['description']}</p> <p class="help-block">${item['description']}</p>
</div> </div>
% elif item['input_type'] == 'button':
<div class="form-group">
<input type="${item['input_type']}" class="btn btn-bright" id="${item['name']}" name="${item['name']}" value="${item['value']}">
</div>
<p class="help-block">${item['description']}</p>
% elif item['input_type'] == 'checkbox': % elif item['input_type'] == 'checkbox':
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" id="${item['name']}" name="${item['name']}" value="1" ${item['value']}> ${item['label']} <input type="checkbox" data-id="${item['name']}" class="checkboxes" value="1" ${helpers.checked(item['value'])}> ${item['label']}
</label> </label>
<p class="help-block">${item['description']}</p> <p class="help-block">${item['description']}</p>
<input type="hidden" id="${item['name']}" name="${item['name']}" value="${item['value']}">
</div> </div>
% endif % endif
% endfor % endfor
@ -35,7 +44,14 @@
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<%
nosave = any(d['input_type'] == 'nosave' for d in data)
%>
% if not nosave:
<input type="button" id="save-notification-item" class="btn btn-bright" value="Save"> <input type="button" id="save-notification-item" class="btn btn-bright" value="Save">
% else:
<input type="button" class="btn btn-bright" data-dismiss="modal" value="Close">
% endif
</div> </div>
</div> </div>
</div> </div>
@ -53,4 +69,30 @@
doAjaxCall('set_notification_config',$(this),'tabs',true); doAjaxCall('set_notification_config',$(this),'tabs',true);
return false; return false;
}); });
$('#twitterStep1').click(function () {
$.get("/twitterStep1", function (data) {window.open(data); })
.done(function () { $('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>Confirm Authorization. Check pop-up blocker if no response.</div>"); });
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
});
$('#twitterStep2').click(function () {
var twitter_key = $("#twitter_key").val();
$.get("/twitterStep2", {'key': twitter_key}, function (data) { $('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>"+data+"</div>"); });
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
});
$('#testTwitter').click(function () {
$.get("/testTwitter",
function (data) { $('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>"+data+"</div>"); });
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
});
// Never send checkbox values directly, always substitute value in hidden input.
$('.checkboxes').click(function() {
var configToggle = $(this).data('id');
if ($(this).is(":checked")) {
$("#"+configToggle).val(1);
} else {
$("#"+configToggle).val(0);
}
});
</script> </script>

View file

@ -34,14 +34,15 @@ available_notification_agents = notifiers.available_notification_agents()
<div class="col-md-4"> <div class="col-md-4">
<ul class="nav-settings list-unstyled" role="tablist"> <ul class="nav-settings list-unstyled" role="tablist">
<li role="presentation" class="active"><a href="#tabs-1" aria-controls="tabs-1" role="tab" data-toggle="tab">General</a></li> <li role="presentation" class="active"><a href="#tabs-1" aria-controls="tabs-1" role="tab" data-toggle="tab">General</a></li>
<li role="presentation"><a href="#tabs-2" aria-controls="tabs-2" role="tab" data-toggle="tab">Web Interface</a></li> <li role="presentation"><a href="#tabs-2" aria-controls="tabs-2" role="tab" data-toggle="tab">Homepage Statistics</a></li>
<li role="presentation"><a href="#tabs-3" aria-controls="tabs-3" role="tab" data-toggle="tab">Access Control</a></li> <li role="presentation"><a href="#tabs-3" aria-controls="tabs-3" role="tab" data-toggle="tab">Web Interface</a></li>
<li role="presentation"><a href="#tabs-4" aria-controls="tabs-4" role="tab" data-toggle="tab">Plex Media Server</a></li> <li role="presentation"><a href="#tabs-4" aria-controls="tabs-4" role="tab" data-toggle="tab">Access Control</a></li>
<li role="presentation"><a href="#tabs-5" aria-controls="tabs-5" role="tab" data-toggle="tab">Plex.tv Account</a></li> <li role="presentation"><a href="#tabs-5" aria-controls="tabs-5" role="tab" data-toggle="tab">Plex Media Server</a></li>
<li role="presentation"><a href="#tabs-6" aria-controls="tabs-6" role="tab" data-toggle="tab">Extra Settings</a></li> <li role="presentation"><a href="#tabs-6" aria-controls="tabs-6" role="tab" data-toggle="tab">Plex.tv Account</a></li>
<li role="presentation"><a href="#tabs-7" aria-controls="tabs-7" role="tab" data-toggle="tab">Monitoring</a></li> <li role="presentation"><a href="#tabs-7" aria-controls="tabs-7" role="tab" data-toggle="tab">Extra Settings</a></li>
<li role="presentation"><a href="#tabs-8" aria-controls="tabs-8" role="tab" data-toggle="tab">Notifications</a></li> <li role="presentation"><a href="#tabs-8" aria-controls="tabs-8" role="tab" data-toggle="tab">Monitoring</a></li>
<li role="presentation"><a href="#tabs-9" aria-controls="tabs-9" role="tab" data-toggle="tab">Notification Agents</a></li> <li role="presentation"><a href="#tabs-9" aria-controls="tabs-9" role="tab" data-toggle="tab">Notifications</a></li>
<li role="presentation"><a href="#tabs-10" aria-controls="tabs-10" role="tab" data-toggle="tab">Notification Agents</a></li>
</ul> </ul>
</div> </div>
<div class="col-md-8"> <div class="col-md-8">
@ -83,10 +84,33 @@ available_notification_agents = notifiers.available_notification_agents()
</div> </div>
<p class="help-block">Set your preferred time format. <a href="javascript:void(0)" data-target="#dateTimeOptionsModal" data-toggle="modal">Click here</a> to see the parameter list.</p> <p class="help-block">Set your preferred time format. <a href="javascript:void(0)" data-target="#dateTimeOptionsModal" data-toggle="modal">Click here</a> to see the parameter list.</p>
</div> </div>
<p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p>
</div>
<div role="tabpanel" class="tab-pane" id="tabs-2">
<div class="padded-header"> <div class="padded-header">
<h3>Homepage Statistics</h3> <h3>Watch Statistics</h3>
</div> </div>
<div class="form-group">
<label for="home_stats_cards">Cards</label>
<div class="row">
<div class="col-md-6">
<select multiple class="form-control" id="home_stats_cards" name="home_stats_cards" data-parsley-trigger="change">
<option id="card-watch_statistics" value="watch_statistics" class="hidden" selected>Watch Statistics</option>
<option id="card-top_tv" value="top_tv">Most Watched TV</option>
<option id="card-popular_tv" value="popular_tv">Most Popular TV</option>
<option id="card-top_movies" value="top_movies">Most Watched Movie</option>
<option id="card-popular_movies" value="popular_movies">Most Popular Movie</option>
<option id="card-top_music" value="top_music">Most Listened Music</option>
<option id="card-popular_music" value="popular_music">Most Popular Music</option>
<option id="card-top_users" value="top_users">Most Active User</option>
<option id="card-top_platforms" value="top_platforms">Most Active Platform</option>
<option id="card-last_watched" value="last_watched">Last Watched</option>
</select>
</div>
</div>
<p class="help-block">Select the cards to show in the watch statistics on the home page. Select none to disable.</p>
</div>
<div class="form-group"> <div class="form-group">
<label for="home_stats_length">Time Frame</label> <label for="home_stats_length">Time Frame</label>
<div class="row"> <div class="row">
@ -95,7 +119,7 @@ available_notification_agents = notifiers.available_notification_agents()
</div> </div>
<div id="home_stats_length_error" class="alert alert-danger settings-alert" role="alert"></div> <div id="home_stats_length_error" class="alert alert-danger settings-alert" role="alert"></div>
</div> </div>
<p class="help-block">Specify the number of days for the statistics on the home page. Default is 30 days.</p> <p class="help-block">Specify the number of days for the watch statistics on the home page. Default is 30 days.</p>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="home_stats_count">Top Lists</label> <label for="home_stats_count">Top Lists</label>
@ -105,7 +129,7 @@ available_notification_agents = notifiers.available_notification_agents()
</div> </div>
<div id="home_stats_count_error" class="alert alert-danger settings-alert" role="alert"></div> <div id="home_stats_count_error" class="alert alert-danger settings-alert" role="alert"></div>
</div> </div>
<p class="help-block">Specify the number of items to show in the top lists for the statistics on the home page. Max is 10 items, default is 5 items, 0 to disable.</p> <p class="help-block">Specify the number of items to show in the top lists for the watch statistics on the home page. Max is 10 items, default is 5 items, 0 to disable.</p>
</div> </div>
<div class="checkbox"> <div class="checkbox">
<label> <label>
@ -113,9 +137,26 @@ available_notification_agents = notifiers.available_notification_agents()
</label> </label>
<p class="help-block">Use play duration instead of play count to generate statistics.</p> <p class="help-block">Use play duration instead of play count to generate statistics.</p>
</div> </div>
<p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p>
<div class="padded-header">
<h3>Library Statistics</h3>
</div> </div>
<div role="tabpanel" class="tab-pane" id="tabs-2">
<div class="form-group">
<label for="home_library_cards">Cards</label>
<div class="row">
<div class="col-md-6">
<select multiple class="form-control" id="home_library_cards" name="home_library_cards" data-parsley-trigger="change">
<option id="card-library_statistics" value="library_statistics" class="hidden" selected>Library Statistics</option>
</select>
</div>
</div>
<p class="help-block">Select the cards to show in the library statistics on the home page. Select none to disable.</p>
</div>
<p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p>
</div>
<div role="tabpanel" class="tab-pane" id="tabs-3">
<div class="padded-header"> <div class="padded-header">
<h3>Web Interface</h3> <h3>Web Interface</h3>
</div> </div>
@ -164,7 +205,7 @@ available_notification_agents = notifiers.available_notification_agents()
<p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p> <p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p>
</div> </div>
<div role="tabpanel" class="tab-pane" id="tabs-3"> <div role="tabpanel" class="tab-pane" id="tabs-4">
<div class="padded-header"> <div class="padded-header">
<h3>Authentication</h3> <h3>Authentication</h3>
@ -216,7 +257,7 @@ available_notification_agents = notifiers.available_notification_agents()
<p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p> <p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p>
</div> </div>
<div role="tabpanel" class="tab-pane" id="tabs-4"> <div role="tabpanel" class="tab-pane" id="tabs-5">
<div class="padded-header"> <div class="padded-header">
<h3>Plex Media Server</h3> <h3>Plex Media Server</h3>
@ -272,7 +313,7 @@ available_notification_agents = notifiers.available_notification_agents()
<input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"> <input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully">
</div> </div>
<div role="tabpanel" class="tab-pane" id="tabs-5"> <div role="tabpanel" class="tab-pane" id="tabs-6">
<div class="padded-header"> <div class="padded-header">
<h3>Plex.tv Authentication</h3> <h3>Plex.tv Authentication</h3>
@ -315,7 +356,7 @@ available_notification_agents = notifiers.available_notification_agents()
<p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p> <p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p>
</div> </div>
<div role="tabpanel" class="tab-pane" id="tabs-6"> <div role="tabpanel" class="tab-pane" id="tabs-7">
<div class="padded-header"> <div class="padded-header">
<h3>Extra Settings</h3> <h3>Extra Settings</h3>
</div> </div>
@ -334,7 +375,7 @@ available_notification_agents = notifiers.available_notification_agents()
<p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p> <p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p>
</div> </div>
<div role="tabpanel" class="tab-pane" id="tabs-7"> <div role="tabpanel" class="tab-pane" id="tabs-8">
<div class="padded-header"> <div class="padded-header">
<h3>Monitoring Settings</h3> <h3>Monitoring Settings</h3>
@ -367,7 +408,7 @@ available_notification_agents = notifiers.available_notification_agents()
</div> </div>
<div id="logging_ignore_interval_error" class="alert alert-danger settings-alert" role="alert"></div> <div id="logging_ignore_interval_error" class="alert alert-danger settings-alert" role="alert"></div>
</div> </div>
<p class="help-block">The interval (in seconds) PlexPy will wait for a video item to be active before logging it. 0 to disable.</p> <p class="help-block">The interval (in seconds) an item must be in a playing state before logging it. 0 to disable.</p>
</div> </div>
<div class="checkbox"> <div class="checkbox">
<label> <label>
@ -416,7 +457,7 @@ available_notification_agents = notifiers.available_notification_agents()
<p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p> <p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p>
</div> </div>
<div role="tabpanel" class="tab-pane" id="tabs-8"> <div role="tabpanel" class="tab-pane" id="tabs-9">
<div class="padded-header"> <div class="padded-header">
<h3>Global Notification Toggles</h3> <h3>Global Notification Toggles</h3>
@ -566,7 +607,7 @@ available_notification_agents = notifiers.available_notification_agents()
<p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p> <p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p>
</div> </div>
<div role="tabpanel" class="tab-pane" id="tabs-9"> <div role="tabpanel" class="tab-pane" id="tabs-10">
<div class="padded-header"> <div class="padded-header">
<h3>Notification Agents</h3> <h3>Notification Agents</h3>
@ -1178,6 +1219,47 @@ $(document).ready(function() {
} }
var accordion = new Accordion($('#accordion'), false); var accordion = new Accordion($('#accordion'), false);
var cards = "${config['home_stats_cards']}".split(/[\s,]+/);
cards.forEach(function (item) {
$('#card-'+item).prop('selected', !$(this).prop('selected'));
});
$('#home_stats_cards').on('mousedown', function(e) {
e.preventDefault();
var scroll = this.scrollTop;
e.target.selected = !e.target.selected;
this.scrollTop = scroll;
}).on('mousemove', function(e) {
e.preventDefault()
});
$.ajax({
url: 'get_server_children',
data: { },
async: true,
complete: function (xhr, status) {
server_children_info = $.parseJSON(xhr.responseText);
libraries_list = server_children_info.libraries_list;
for (var i in libraries_list) {
title = libraries_list[i].title;
key = libraries_list[i].key;
$('#home_library_cards').append('<option id="card-' + key + '" value="' + key + '">' + title + '</option>')
}
var cards = "${config['home_library_cards']}".split(/[\s,]+/);
cards.forEach(function (item) {
$('#card-'+item).prop('selected', !$(this).prop('selected'));
});
}
});
$('#home_library_cards').on('mousedown', function(e) {
e.preventDefault();
var scroll = this.scrollTop;
e.target.selected = !e.target.selected;
this.scrollTop = scroll;
}).on('mousemove', function(e) {
e.preventDefault()
});
}); });
</script> </script>
</%def> </%def>

View file

@ -17,11 +17,16 @@ import os
import sys import sys
import subprocess import subprocess
import threading import threading
import webbrowser
import sqlite3 import sqlite3
import cherrypy import cherrypy
import datetime import datetime
import uuid import uuid
# Some cut down versions of Python may not include this module and it's not critical for us
try:
import webbrowser
no_browser = False
except ImportError:
no_browser = True
from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.interval import IntervalTrigger from apscheduler.triggers.interval import IntervalTrigger
@ -228,6 +233,7 @@ def daemonize():
def launch_browser(host, port, root): def launch_browser(host, port, root):
if not no_browser:
if host == '0.0.0.0': if host == '0.0.0.0':
host = 'localhost' host = 'localhost'

View file

@ -82,9 +82,11 @@ _CONFIG_DEFINITIONS = {
'GROWL_ON_RESUME': (int, 'Growl', 0), 'GROWL_ON_RESUME': (int, 'Growl', 0),
'GROWL_ON_BUFFER': (int, 'Growl', 0), 'GROWL_ON_BUFFER': (int, 'Growl', 0),
'GROWL_ON_WATCHED': (int, 'Growl', 0), 'GROWL_ON_WATCHED': (int, 'Growl', 0),
'HOME_LIBRARY_CARDS': (str, 'General', 'library_statistics_first'),
'HOME_STATS_LENGTH': (int, 'General', 30), 'HOME_STATS_LENGTH': (int, 'General', 30),
'HOME_STATS_TYPE': (int, 'General', 0), 'HOME_STATS_TYPE': (int, 'General', 0),
'HOME_STATS_COUNT': (int, 'General', 5), 'HOME_STATS_COUNT': (int, 'General', 5),
'HOME_STATS_CARDS': (str, 'General', 'watch_statistics, top_tv, popular_tv, top_movies, popular_movies, top_music, popular_music, top_users, top_platforms, last_watched'),
'HTTPS_CERT': (str, 'General', ''), 'HTTPS_CERT': (str, 'General', ''),
'HTTPS_KEY': (str, 'General', ''), 'HTTPS_KEY': (str, 'General', ''),
'HTTP_HOST': (str, 'General', '0.0.0.0'), 'HTTP_HOST': (str, 'General', '0.0.0.0'),
@ -194,8 +196,14 @@ _CONFIG_DEFINITIONS = {
'TV_NOTIFY_ON_PAUSE': (int, 'Monitoring', 0), 'TV_NOTIFY_ON_PAUSE': (int, 'Monitoring', 0),
'TWITTER_ENABLED': (int, 'Twitter', 0), 'TWITTER_ENABLED': (int, 'Twitter', 0),
'TWITTER_PASSWORD': (str, 'Twitter', ''), 'TWITTER_PASSWORD': (str, 'Twitter', ''),
'TWITTER_PREFIX': (str, 'Twitter', 'Headphones'), 'TWITTER_PREFIX': (str, 'Twitter', 'PlexPy'),
'TWITTER_USERNAME': (str, 'Twitter', ''), 'TWITTER_USERNAME': (str, 'Twitter', ''),
'TWITTER_ON_PLAY': (int, 'Twitter', 0),
'TWITTER_ON_STOP': (int, 'Twitter', 0),
'TWITTER_ON_PAUSE': (int, 'Twitter', 0),
'TWITTER_ON_RESUME': (int, 'Twitter', 0),
'TWITTER_ON_BUFFER': (int, 'Twitter', 0),
'TWITTER_ON_WATCHED': (int, 'Twitter', 0),
'UPDATE_DB_INTERVAL': (int, 'General', 24), 'UPDATE_DB_INTERVAL': (int, 'General', 24),
'VERIFY_SSL_CERT': (bool_int, 'Advanced', 1), 'VERIFY_SSL_CERT': (bool_int, 'Advanced', 1),
'VIDEO_LOGGING_ENABLE': (int, 'Monitoring', 1), 'VIDEO_LOGGING_ENABLE': (int, 'Monitoring', 1),

View file

@ -131,22 +131,14 @@ class DataFactory(object):
return dict return dict
def get_home_stats(self, time_range='30', stat_type='0', stat_count='5'): def get_home_stats(self, time_range='30', stats_type=0, stats_count='5', stats_cards='', notify_watched_percent='85'):
monitor_db = database.MonitorDatabase() monitor_db = database.MonitorDatabase()
if not time_range.isdigit(): sort_type = 'total_plays' if stats_type == 0 else 'total_duration'
time_range = '30'
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", "last_watched"]
home_stats = [] home_stats = []
for stat in stats_queries: for stat in stats_cards:
if 'top_tv' in stat: if 'top_tv' in stat:
top_tv = [] top_tv = []
try: try:
@ -166,7 +158,7 @@ class DataFactory(object):
'>= datetime("now", "-%s days", "localtime") ' \ '>= datetime("now", "-%s days", "localtime") ' \
'AND session_history_metadata.media_type = "episode" ' \ 'AND session_history_metadata.media_type = "episode" ' \
'GROUP BY session_history_metadata.grandparent_title ' \ 'GROUP BY session_history_metadata.grandparent_title ' \
'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stat_count) 'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stats_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except: except:
logger.warn("Unable to execute database query.") logger.warn("Unable to execute database query.")
@ -202,6 +194,10 @@ class DataFactory(object):
'session_history_metadata.grandparent_rating_key, ' \ 'session_history_metadata.grandparent_rating_key, ' \
'MAX(session_history.started) as last_watch, ' \ 'MAX(session_history.started) as last_watch, ' \
'COUNT(session_history.id) as total_plays, ' \ 'COUNT(session_history.id) as total_plays, ' \
'SUM(case when session_history.stopped > 0 ' \
'then (session_history.stopped - session_history.started) ' \
' - (case when session_history.paused_counter is NULL then 0 else session_history.paused_counter end) ' \
'else 0 end) as total_duration, ' \
'session_history_metadata.grandparent_thumb ' \ 'session_history_metadata.grandparent_thumb ' \
'FROM session_history_metadata ' \ 'FROM session_history_metadata ' \
'JOIN session_history ON session_history_metadata.id = session_history.id ' \ 'JOIN session_history ON session_history_metadata.id = session_history.id ' \
@ -209,8 +205,8 @@ class DataFactory(object):
'>= datetime("now", "-%s days", "localtime") ' \ '>= datetime("now", "-%s days", "localtime") ' \
'AND session_history_metadata.media_type = "episode" ' \ 'AND session_history_metadata.media_type = "episode" ' \
'GROUP BY session_history_metadata.grandparent_title ' \ 'GROUP BY session_history_metadata.grandparent_title ' \
'ORDER BY users_watched DESC, total_plays DESC ' \ 'ORDER BY users_watched DESC, %s DESC ' \
'LIMIT %s' % (time_range, stat_count) 'LIMIT %s' % (time_range, sort_type, stats_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except: except:
logger.warn("Unable to execute database query.") logger.warn("Unable to execute database query.")
@ -222,7 +218,7 @@ class DataFactory(object):
'rating_key': item[3], 'rating_key': item[3],
'last_play': item[4], 'last_play': item[4],
'total_plays': item[5], 'total_plays': item[5],
'grandparent_thumb': item[6], 'grandparent_thumb': item[7],
'thumb': '', 'thumb': '',
'user': '', 'user': '',
'friendly_name': '', 'friendly_name': '',
@ -254,7 +250,7 @@ class DataFactory(object):
'>= datetime("now", "-%s days", "localtime") ' \ '>= datetime("now", "-%s days", "localtime") ' \
'AND session_history_metadata.media_type = "movie" ' \ 'AND session_history_metadata.media_type = "movie" ' \
'GROUP BY session_history_metadata.full_title ' \ 'GROUP BY session_history_metadata.full_title ' \
'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stat_count) 'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stats_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except: except:
logger.warn("Unable to execute database query.") logger.warn("Unable to execute database query.")
@ -290,6 +286,10 @@ class DataFactory(object):
'session_history_metadata.rating_key, ' \ 'session_history_metadata.rating_key, ' \
'MAX(session_history.started) as last_watch, ' \ 'MAX(session_history.started) as last_watch, ' \
'COUNT(session_history.id) as total_plays, ' \ 'COUNT(session_history.id) as total_plays, ' \
'SUM(case when session_history.stopped > 0 ' \
'then (session_history.stopped - session_history.started) ' \
' - (case when session_history.paused_counter is NULL then 0 else session_history.paused_counter end) ' \
'else 0 end) as total_duration, ' \
'session_history_metadata.thumb ' \ 'session_history_metadata.thumb ' \
'FROM session_history_metadata ' \ 'FROM session_history_metadata ' \
'JOIN session_history ON session_history_metadata.id = session_history.id ' \ 'JOIN session_history ON session_history_metadata.id = session_history.id ' \
@ -297,8 +297,8 @@ class DataFactory(object):
'>= datetime("now", "-%s days", "localtime") ' \ '>= datetime("now", "-%s days", "localtime") ' \
'AND session_history_metadata.media_type = "movie" ' \ 'AND session_history_metadata.media_type = "movie" ' \
'GROUP BY session_history_metadata.full_title ' \ 'GROUP BY session_history_metadata.full_title ' \
'ORDER BY users_watched DESC, total_plays DESC ' \ 'ORDER BY users_watched DESC, %s DESC ' \
'LIMIT %s' % (time_range, stat_count) 'LIMIT %s' % (time_range, sort_type, stats_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except: except:
logger.warn("Unable to execute database query.") logger.warn("Unable to execute database query.")
@ -311,7 +311,7 @@ class DataFactory(object):
'last_play': item[4], 'last_play': item[4],
'total_plays': item[5], 'total_plays': item[5],
'grandparent_thumb': '', 'grandparent_thumb': '',
'thumb': item[6], 'thumb': item[7],
'user': '', 'user': '',
'friendly_name': '', 'friendly_name': '',
'platform_type': '', 'platform_type': '',
@ -323,6 +323,98 @@ class DataFactory(object):
home_stats.append({'stat_id': stat, home_stats.append({'stat_id': stat,
'rows': popular_movies}) 'rows': popular_movies})
elif 'top_music' in stat:
top_music = []
try:
query = 'SELECT session_history_metadata.id, ' \
'session_history_metadata.grandparent_title, ' \
'COUNT(session_history_metadata.grandparent_title) as total_plays, ' \
'SUM(case when session_history.stopped > 0 ' \
'then (session_history.stopped - session_history.started) ' \
' - (case when session_history.paused_counter is NULL then 0 else session_history.paused_counter end) ' \
'else 0 end) as total_duration, ' \
'session_history_metadata.grandparent_rating_key, ' \
'MAX(session_history.started) as last_watch,' \
'session_history_metadata.grandparent_thumb ' \
'FROM session_history_metadata ' \
'JOIN session_history on session_history_metadata.id = session_history.id ' \
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \
'>= datetime("now", "-%s days", "localtime") ' \
'AND session_history_metadata.media_type = "track" ' \
'GROUP BY session_history_metadata.grandparent_title ' \
'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stats_count)
result = monitor_db.select(query)
except:
logger.warn("Unable to execute database query.")
return None
for item in result:
row = {'title': item[1],
'total_plays': item[2],
'total_duration': item[3],
'users_watched': '',
'rating_key': item[4],
'last_play': item[5],
'grandparent_thumb': item[6],
'thumb': '',
'user': '',
'friendly_name': '',
'platform_type': '',
'platform': '',
'row_id': item[0]
}
top_music.append(row)
home_stats.append({'stat_id': stat,
'stat_type': sort_type,
'rows': top_music})
elif 'popular_music' in stat:
popular_music = []
try:
query = 'SELECT session_history_metadata.id, ' \
'session_history_metadata.grandparent_title, ' \
'COUNT(DISTINCT session_history.user_id) as users_watched, ' \
'session_history_metadata.grandparent_rating_key, ' \
'MAX(session_history.started) as last_watch, ' \
'COUNT(session_history.id) as total_plays, ' \
'SUM(case when session_history.stopped > 0 ' \
'then (session_history.stopped - session_history.started) ' \
' - (case when session_history.paused_counter is NULL then 0 else session_history.paused_counter end) ' \
'else 0 end) as total_duration, ' \
'session_history_metadata.grandparent_thumb ' \
'FROM session_history_metadata ' \
'JOIN session_history ON session_history_metadata.id = session_history.id ' \
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \
'>= datetime("now", "-%s days", "localtime") ' \
'AND session_history_metadata.media_type = "track" ' \
'GROUP BY session_history_metadata.grandparent_title ' \
'ORDER BY users_watched DESC, %s DESC ' \
'LIMIT %s' % (time_range, sort_type, stats_count)
result = monitor_db.select(query)
except:
logger.warn("Unable to execute database query.")
return None
for item in result:
row = {'title': item[1],
'users_watched': item[2],
'rating_key': item[3],
'last_play': item[4],
'total_plays': item[5],
'grandparent_thumb': item[7],
'thumb': '',
'user': '',
'friendly_name': '',
'platform_type': '',
'platform': '',
'row_id': item[0]
}
popular_music.append(row)
home_stats.append({'stat_id': stat,
'rows': popular_music})
elif 'top_users' in stat: elif 'top_users' in stat:
top_users = [] top_users = []
try: try:
@ -343,7 +435,7 @@ class DataFactory(object):
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") >= ' \ 'WHERE datetime(session_history.stopped, "unixepoch", "localtime") >= ' \
'datetime("now", "-%s days", "localtime") '\ 'datetime("now", "-%s days", "localtime") '\
'GROUP BY session_history.user_id ' \ 'GROUP BY session_history.user_id ' \
'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stat_count) 'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stats_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except: except:
logger.warn("Unable to execute database query.") logger.warn("Unable to execute database query.")
@ -391,7 +483,7 @@ class DataFactory(object):
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ 'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \
'>= datetime("now", "-%s days", "localtime") ' \ '>= datetime("now", "-%s days", "localtime") ' \
'GROUP BY session_history.platform ' \ 'GROUP BY session_history.platform ' \
'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stat_count) 'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stats_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except: except:
logger.warn("Unable to execute database query.") logger.warn("Unable to execute database query.")
@ -432,7 +524,11 @@ class DataFactory(object):
'session_history_metadata.thumb, ' \ 'session_history_metadata.thumb, ' \
'session_history_metadata.grandparent_thumb, ' \ 'session_history_metadata.grandparent_thumb, ' \
'MAX(session_history.started) as last_watch, ' \ 'MAX(session_history.started) as last_watch, ' \
'session_history.player as platform ' \ 'session_history.player as platform, ' \
'((CASE WHEN session_history.view_offset IS NULL THEN 0.1 ELSE \
session_history.view_offset * 1.0 END) / \
(CASE WHEN session_history_metadata.duration IS NULL THEN 1.0 ELSE \
session_history_metadata.duration * 1.0 END) * 100) as percent_complete ' \
'FROM session_history_metadata ' \ 'FROM session_history_metadata ' \
'JOIN session_history ON session_history_metadata.id = session_history.id ' \ 'JOIN session_history ON session_history_metadata.id = session_history.id ' \
'LEFT OUTER JOIN users ON session_history.user_id = users.user_id ' \ 'LEFT OUTER JOIN users ON session_history.user_id = users.user_id ' \
@ -440,9 +536,10 @@ class DataFactory(object):
'>= datetime("now", "-%s days", "localtime") ' \ '>= datetime("now", "-%s days", "localtime") ' \
'AND (session_history_metadata.media_type = "movie" ' \ 'AND (session_history_metadata.media_type = "movie" ' \
'OR session_history_metadata.media_type = "episode") ' \ 'OR session_history_metadata.media_type = "episode") ' \
'AND percent_complete >= %s ' \
'GROUP BY session_history_metadata.full_title ' \ 'GROUP BY session_history_metadata.full_title ' \
'ORDER BY last_watch DESC ' \ 'ORDER BY last_watch DESC ' \
'LIMIT %s' % (time_range, stat_count) 'LIMIT %s' % (time_range, notify_watched_percent, stats_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except: except:
logger.warn("Unable to execute database query.") logger.warn("Unable to execute database query.")

View file

@ -241,7 +241,7 @@ class MonitorProcessing(object):
if is_import: if is_import:
if str(session['stopped']).isdigit(): if str(session['stopped']).isdigit():
stopped = session['stopped'] stopped = int(session['stopped'])
else: else:
stopped = int(time.time()) stopped = int(time.time())
else: else:
@ -257,21 +257,25 @@ class MonitorProcessing(object):
logger.debug(u"PlexPy Monitor :: ratingKey %s not logged. Does not meet logging criteria. " logger.debug(u"PlexPy Monitor :: ratingKey %s not logged. Does not meet logging criteria. "
u"Media type is '%s'" % (session['rating_key'], session['media_type'])) u"Media type is '%s'" % (session['rating_key'], session['media_type']))
if str(session['paused_counter']).isdigit():
real_play_time = stopped - session['started'] - int(session['paused_counter'])
else:
real_play_time = stopped - session['started']
if plexpy.CONFIG.LOGGING_IGNORE_INTERVAL and not is_import: if plexpy.CONFIG.LOGGING_IGNORE_INTERVAL and not is_import:
if (session['media_type'] == 'movie' or session['media_type'] == 'episode') and \ if (session['media_type'] == 'movie' or session['media_type'] == 'episode') and \
(int(stopped) - session['started'] < int(plexpy.CONFIG.LOGGING_IGNORE_INTERVAL)): (real_play_time < int(plexpy.CONFIG.LOGGING_IGNORE_INTERVAL)):
logging_enabled = False logging_enabled = False
logger.debug(u"PlexPy Monitor :: Play duration for ratingKey %s is %s secs which is less than %s " logger.debug(u"PlexPy Monitor :: Play duration for ratingKey %s is %s secs which is less than %s "
u"seconds, so we're not logging it." % u"seconds, so we're not logging it." %
(session['rating_key'], str(int(stopped) - session['started']), (session['rating_key'], str(real_play_time), plexpy.CONFIG.LOGGING_IGNORE_INTERVAL))
plexpy.CONFIG.LOGGING_IGNORE_INTERVAL))
elif is_import and import_ignore_interval: elif is_import and import_ignore_interval:
if (session['media_type'] == 'movie' or session['media_type'] == 'episode') and \ if (session['media_type'] == 'movie' or session['media_type'] == 'episode') and \
(int(stopped) - session['started'] < int(import_ignore_interval)): (real_play_time < int(import_ignore_interval)):
logging_enabled = False logging_enabled = False
logger.debug(u"PlexPy Monitor :: Play duration for ratingKey %s is %s secs which is less than %s " logger.debug(u"PlexPy Monitor :: Play duration for ratingKey %s is %s secs which is less than %s "
u"seconds, so we're not logging it." % u"seconds, so we're not logging it." %
(session['rating_key'], str(int(stopped) - session['started']), (session['rating_key'], str(real_play_time),
import_ignore_interval)) import_ignore_interval))
if not user_details['keep_history'] and not is_import: if not user_details['keep_history'] and not is_import:

View file

@ -49,7 +49,8 @@ AGENT_IDS = {"Growl": 0,
"Pushover": 7, "Pushover": 7,
"OSX Notify": 8, "OSX Notify": 8,
"Boxcar2": 9, "Boxcar2": 9,
"Email": 10} "Email": 10,
"Twitter": 11}
def available_notification_agents(): def available_notification_agents():
agents = [{'name': 'Growl', agents = [{'name': 'Growl',
@ -171,6 +172,18 @@ def available_notification_agents():
'on_resume': plexpy.CONFIG.EMAIL_ON_RESUME, 'on_resume': plexpy.CONFIG.EMAIL_ON_RESUME,
'on_buffer': plexpy.CONFIG.EMAIL_ON_BUFFER, 'on_buffer': plexpy.CONFIG.EMAIL_ON_BUFFER,
'on_watched': plexpy.CONFIG.EMAIL_ON_WATCHED 'on_watched': plexpy.CONFIG.EMAIL_ON_WATCHED
},
{'name': 'Twitter',
'id': AGENT_IDS['Twitter'],
'config_prefix': 'twitter',
'has_config': True,
'state': checked(plexpy.CONFIG.TWITTER_ENABLED),
'on_play': plexpy.CONFIG.TWITTER_ON_PLAY,
'on_stop': plexpy.CONFIG.TWITTER_ON_STOP,
'on_pause': plexpy.CONFIG.TWITTER_ON_PAUSE,
'on_resume': plexpy.CONFIG.TWITTER_ON_RESUME,
'on_buffer': plexpy.CONFIG.TWITTER_ON_BUFFER,
'on_watched': plexpy.CONFIG.TWITTER_ON_WATCHED
} }
] ]
@ -229,6 +242,9 @@ def get_notification_agent_config(config_id):
elif config_id == 10: elif config_id == 10:
email = Email() email = Email()
return email.return_config_options() return email.return_config_options()
elif config_id == 11:
tweet = TwitterNotifier()
return tweet.return_config_options()
else: else:
return [] return []
else: else:
@ -271,6 +287,9 @@ def send_notification(config_id, subject, body):
elif config_id == 10: elif config_id == 10:
email = Email() email = Email()
email.notify(subject=subject, message=body) email.notify(subject=subject, message=body)
elif config_id == 11:
tweet = TwitterNotifier()
tweet.notify(subject=subject, message=body)
else: else:
logger.debug(u"PlexPy Notifier :: Unknown agent id received.") logger.debug(u"PlexPy Notifier :: Unknown agent id received.")
else: else:
@ -912,19 +931,17 @@ class TwitterNotifier(object):
SIGNIN_URL = 'https://api.twitter.com/oauth/authenticate' SIGNIN_URL = 'https://api.twitter.com/oauth/authenticate'
def __init__(self): def __init__(self):
self.consumer_key = "oYKnp2ddX5gbARjqX8ZAAg" self.consumer_key = "2LdJKXHDUwJtjYBsdwJisIOsh"
self.consumer_secret = "A4Xkw9i5SjHbTk7XT8zzOPqivhj9MmRDR9Qn95YA9sk" self.consumer_secret = "QWbUcZzAIiL4zbDCIhy2EdUkV8yEEav3qMdo5y3FugxCFelWrA"
def notify_snatch(self, title): def notify(self, subject, message):
if plexpy.CONFIG.TWITTER_ONSNATCH: if not subject or not message:
self._notifyTwitter(common.notifyStrings[common.NOTIFY_SNATCH] + ': ' + title + ' at ' + helpers.now()) return
else:
def notify_download(self, title): self._send_tweet(subject + ': ' + message)
if plexpy.CONFIG.TWITTER_ENABLED:
self._notifyTwitter(common.notifyStrings[common.NOTIFY_DOWNLOAD] + ': ' + title + ' at ' + helpers.now())
def test_notify(self): def test_notify(self):
return self._notifyTwitter("This is a test notification from PlexPy at " + helpers.now(), force=True) return self._send_tweet("This is a test notification from PlexPy at " + helpers.now())
def _get_authorization(self): def _get_authorization(self):
@ -958,7 +975,7 @@ class TwitterNotifier(object):
logger.info('Generating and signing request for an access token using key ' + key) logger.info('Generating and signing request for an access token using key ' + key)
oauth_consumer = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret) oauth_consumer = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret)
logger.info('oauth_consumer: ' + str(oauth_consumer)) # logger.debug('oauth_consumer: ' + str(oauth_consumer))
oauth_client = oauth.Client(oauth_consumer, token) oauth_client = oauth.Client(oauth_consumer, token)
logger.info('oauth_client: ' + str(oauth_client)) logger.info('oauth_client: ' + str(oauth_client))
resp, content = oauth_client.request(self.ACCESS_TOKEN_URL, method='POST', body='oauth_verifier=%s' % key) resp, content = oauth_client.request(self.ACCESS_TOKEN_URL, method='POST', body='oauth_verifier=%s' % key)
@ -979,7 +996,6 @@ class TwitterNotifier(object):
return True return True
def _send_tweet(self, message=None): def _send_tweet(self, message=None):
username = self.consumer_key username = self.consumer_key
password = self.consumer_secret password = self.consumer_secret
access_token_key = plexpy.CONFIG.TWITTER_USERNAME access_token_key = plexpy.CONFIG.TWITTER_USERNAME
@ -997,13 +1013,36 @@ class TwitterNotifier(object):
return True return True
def _notifyTwitter(self, message='', force=False): def return_config_options(self):
prefix = plexpy.CONFIG.TWITTER_PREFIX config_option = [{'label': 'Request Authorisation',
'value': 'Request Authorisation',
'name': 'twitterStep1',
'description': 'Step 1: Click Request button above. (Ensure you allow the browser pop-up).',
'input_type': 'button'
},
{'label': 'Authorisation Key',
'value': '',
'name': 'twitter_key',
'description': 'Step 2: Input the authorisation key you received from Step 1.',
'input_type': 'text'
},
{'label': 'Verify Key',
'value': 'Verify Key',
'name': 'twitterStep2',
'description': 'Step 3: Verify the key.',
'input_type': 'button'
},
{'label': 'Test Twitter',
'value': 'Test Twitter',
'name': 'testTwitter',
'description': 'Test if Twitter notifications are working. See logs for troubleshooting.',
'input_type': 'button'
},
{'input_type': 'nosave'
}
]
if not plexpy.CONFIG.TWITTER_ENABLED and not force: return config_option
return False
return self._send_tweet(prefix + ": " + message)
class OSX_NOTIFY(object): class OSX_NOTIFY(object):
@ -1204,7 +1243,7 @@ class Email(object):
'input_type': 'password' 'input_type': 'password'
}, },
{'label': 'TLS', {'label': 'TLS',
'value': checked(plexpy.CONFIG.EMAIL_TLS), 'value': plexpy.CONFIG.EMAIL_TLS,
'name': 'email_tls', 'name': 'email_tls',
'description': 'Does the server use encryption.', 'description': 'Does the server use encryption.',
'input_type': 'checkbox' 'input_type': 'checkbox'

View file

@ -1270,11 +1270,11 @@ class PmsConnect(object):
return output return output
""" """
Return processed and validated server statistics. Return processed and validated library statistics.
Output: array Output: array
""" """
def get_library_stats(self): def get_library_stats(self, library_cards=''):
server_libraries = self.get_server_children() server_libraries = self.get_server_children()
server_library_stats = [] server_library_stats = []
@ -1285,7 +1285,10 @@ class PmsConnect(object):
for library in libraries_list: for library in libraries_list:
library_type = library['type'] library_type = library['type']
section_key = library['key'] section_key = library['key']
if section_key in library_cards:
library_list = self.get_library_children(library_type, section_key) library_list = self.get_library_children(library_type, section_key)
else:
continue
if library_list['library_count'] != '0': if library_list['library_count'] != '0':
library_stats = {'title': library['title'], library_stats = {'title': library['title'],

View file

@ -1,2 +1,2 @@
PLEXPY_VERSION = "master" PLEXPY_VERSION = "master"
PLEXPY_RELEASE_VERSION = "1.1.9" PLEXPY_RELEASE_VERSION = "1.1.10"

View file

@ -66,9 +66,9 @@ class WebInterface(object):
def home(self): def home(self):
config = { config = {
"home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH, "home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH,
"home_stats_type": plexpy.CONFIG.HOME_STATS_TYPE, "home_stats_cards": plexpy.CONFIG.HOME_STATS_CARDS,
"home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT, "home_library_cards": plexpy.CONFIG.HOME_LIBRARY_CARDS,
"pms_identifier": plexpy.CONFIG.PMS_IDENTIFIER, "pms_identifier": plexpy.CONFIG.PMS_IDENTIFIER
} }
return serve_template(templatename="index.html", title="Home", config=config) return serve_template(templatename="index.html", title="Home", config=config)
@ -121,16 +121,41 @@ class WebInterface(object):
return json.dumps(formats) return json.dumps(formats)
@cherrypy.expose @cherrypy.expose
def home_stats(self, time_range='30', stat_type='0', stat_count='5', **kwargs): def home_stats(self, **kwargs):
data_factory = datafactory.DataFactory() data_factory = datafactory.DataFactory()
stats_data = data_factory.get_home_stats(time_range=time_range, stat_type=stat_type, stat_count=stat_count)
time_range = plexpy.CONFIG.HOME_STATS_LENGTH
stats_type = plexpy.CONFIG.HOME_STATS_TYPE
stats_count = plexpy.CONFIG.HOME_STATS_COUNT
stats_cards = plexpy.CONFIG.HOME_STATS_CARDS.split(', ')
notify_watched_percent = plexpy.CONFIG.NOTIFY_WATCHED_PERCENT
stats_data = data_factory.get_home_stats(time_range=time_range,
stats_type=stats_type,
stats_count=stats_count,
stats_cards=stats_cards,
notify_watched_percent=notify_watched_percent)
return serve_template(templatename="home_stats.html", title="Stats", data=stats_data) return serve_template(templatename="home_stats.html", title="Stats", data=stats_data)
@cherrypy.expose @cherrypy.expose
def library_stats(self, **kwargs): def library_stats(self, **kwargs):
pms_connect = pmsconnect.PmsConnect() pms_connect = pmsconnect.PmsConnect()
stats_data = pms_connect.get_library_stats()
library_cards = plexpy.CONFIG.HOME_LIBRARY_CARDS.split(', ')
if library_cards == ['library_statistics_first']:
library_cards = ['library_statistics']
server_children = pms_connect.get_server_children()
server_libraries = server_children['libraries_list']
for library in server_libraries:
library_cards.append(library['key'])
plexpy.CONFIG.HOME_LIBRARY_CARDS = ', '.join(library_cards)
plexpy.CONFIG.write()
stats_data = pms_connect.get_library_stats(library_cards=library_cards)
return serve_template(templatename="library_stats.html", title="Library Stats", data=stats_data) return serve_template(templatename="library_stats.html", title="Library Stats", data=stats_data)
@ -144,7 +169,12 @@ class WebInterface(object):
@cherrypy.expose @cherrypy.expose
def graphs(self): def graphs(self):
return serve_template(templatename="graphs.html", title="Graphs")
config = {
"music_logging_enable": plexpy.CONFIG.MUSIC_LOGGING_ENABLE
}
return serve_template(templatename="graphs.html", title="Graphs", config=config)
@cherrypy.expose @cherrypy.expose
def sync(self): def sync(self):
@ -373,46 +403,7 @@ class WebInterface(object):
"cache_dir": plexpy.CONFIG.CACHE_DIR, "cache_dir": plexpy.CONFIG.CACHE_DIR,
"check_github": checked(plexpy.CONFIG.CHECK_GITHUB), "check_github": checked(plexpy.CONFIG.CHECK_GITHUB),
"interface_list": interface_list, "interface_list": interface_list,
"growl_enabled": checked(plexpy.CONFIG.GROWL_ENABLED),
"growl_host": plexpy.CONFIG.GROWL_HOST,
"growl_password": plexpy.CONFIG.GROWL_PASSWORD,
"prowl_enabled": checked(plexpy.CONFIG.PROWL_ENABLED),
"prowl_keys": plexpy.CONFIG.PROWL_KEYS,
"prowl_priority": plexpy.CONFIG.PROWL_PRIORITY,
"xbmc_enabled": checked(plexpy.CONFIG.XBMC_ENABLED),
"xbmc_host": plexpy.CONFIG.XBMC_HOST,
"xbmc_username": plexpy.CONFIG.XBMC_USERNAME,
"xbmc_password": plexpy.CONFIG.XBMC_PASSWORD,
"plex_enabled": checked(plexpy.CONFIG.PLEX_ENABLED),
"plex_client_host": plexpy.CONFIG.PLEX_CLIENT_HOST,
"plex_username": plexpy.CONFIG.PLEX_USERNAME,
"plex_password": plexpy.CONFIG.PLEX_PASSWORD,
"nma_enabled": checked(plexpy.CONFIG.NMA_ENABLED),
"nma_apikey": plexpy.CONFIG.NMA_APIKEY,
"nma_priority": int(plexpy.CONFIG.NMA_PRIORITY),
"pushalot_enabled": checked(plexpy.CONFIG.PUSHALOT_ENABLED),
"pushalot_apikey": plexpy.CONFIG.PUSHALOT_APIKEY,
"pushover_enabled": checked(plexpy.CONFIG.PUSHOVER_ENABLED),
"pushover_keys": plexpy.CONFIG.PUSHOVER_KEYS,
"pushover_apitoken": plexpy.CONFIG.PUSHOVER_APITOKEN,
"pushover_priority": plexpy.CONFIG.PUSHOVER_PRIORITY,
"pushbullet_enabled": checked(plexpy.CONFIG.PUSHBULLET_ENABLED),
"pushbullet_apikey": plexpy.CONFIG.PUSHBULLET_APIKEY,
"pushbullet_deviceid": plexpy.CONFIG.PUSHBULLET_DEVICEID,
"twitter_enabled": checked(plexpy.CONFIG.TWITTER_ENABLED),
"osx_notify_enabled": checked(plexpy.CONFIG.OSX_NOTIFY_ENABLED),
"osx_notify_app": plexpy.CONFIG.OSX_NOTIFY_APP,
"boxcar_enabled": checked(plexpy.CONFIG.BOXCAR_ENABLED),
"boxcar_token": plexpy.CONFIG.BOXCAR_TOKEN,
"cache_sizemb": plexpy.CONFIG.CACHE_SIZEMB, "cache_sizemb": plexpy.CONFIG.CACHE_SIZEMB,
"email_enabled": checked(plexpy.CONFIG.EMAIL_ENABLED),
"email_from": plexpy.CONFIG.EMAIL_FROM,
"email_to": plexpy.CONFIG.EMAIL_TO,
"email_smtp_server": plexpy.CONFIG.EMAIL_SMTP_SERVER,
"email_smtp_user": plexpy.CONFIG.EMAIL_SMTP_USER,
"email_smtp_password": plexpy.CONFIG.EMAIL_SMTP_PASSWORD,
"email_smtp_port": int(plexpy.CONFIG.EMAIL_SMTP_PORT),
"email_tls": checked(plexpy.CONFIG.EMAIL_TLS),
"pms_identifier": plexpy.CONFIG.PMS_IDENTIFIER, "pms_identifier": plexpy.CONFIG.PMS_IDENTIFIER,
"pms_ip": plexpy.CONFIG.PMS_IP, "pms_ip": plexpy.CONFIG.PMS_IP,
"pms_logs_folder": plexpy.CONFIG.PMS_LOGS_FOLDER, "pms_logs_folder": plexpy.CONFIG.PMS_LOGS_FOLDER,
@ -421,7 +412,6 @@ class WebInterface(object):
"pms_ssl": checked(plexpy.CONFIG.PMS_SSL), "pms_ssl": checked(plexpy.CONFIG.PMS_SSL),
"pms_use_bif": checked(plexpy.CONFIG.PMS_USE_BIF), "pms_use_bif": checked(plexpy.CONFIG.PMS_USE_BIF),
"pms_uuid": plexpy.CONFIG.PMS_UUID, "pms_uuid": plexpy.CONFIG.PMS_UUID,
"plexwatch_database": plexpy.CONFIG.PLEXWATCH_DATABASE,
"date_format": plexpy.CONFIG.DATE_FORMAT, "date_format": plexpy.CONFIG.DATE_FORMAT,
"time_format": plexpy.CONFIG.TIME_FORMAT, "time_format": plexpy.CONFIG.TIME_FORMAT,
"grouping_global_history": checked(plexpy.CONFIG.GROUPING_GLOBAL_HISTORY), "grouping_global_history": checked(plexpy.CONFIG.GROUPING_GLOBAL_HISTORY),
@ -463,6 +453,8 @@ class WebInterface(object):
"home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH, "home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH,
"home_stats_type": checked(plexpy.CONFIG.HOME_STATS_TYPE), "home_stats_type": checked(plexpy.CONFIG.HOME_STATS_TYPE),
"home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT, "home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT,
"home_stats_cards": plexpy.CONFIG.HOME_STATS_CARDS,
"home_library_cards": plexpy.CONFIG.HOME_LIBRARY_CARDS,
"buffer_threshold": plexpy.CONFIG.BUFFER_THRESHOLD, "buffer_threshold": plexpy.CONFIG.BUFFER_THRESHOLD,
"buffer_wait": plexpy.CONFIG.BUFFER_WAIT "buffer_wait": plexpy.CONFIG.BUFFER_WAIT
} }
@ -474,12 +466,7 @@ class WebInterface(object):
# Handle the variable config options. Note - keys with False values aren't getting passed # Handle the variable config options. Note - keys with False values aren't getting passed
checked_configs = [ checked_configs = [
"launch_browser", "enable_https", "api_enabled", "freeze_db", "growl_enabled", "launch_browser", "enable_https", "api_enabled", "freeze_db", "check_github",
"prowl_enabled", "xbmc_enabled", "check_github",
"plex_enabled", "nma_enabled", "pushalot_enabled",
"pushover_enabled", "pushbullet_enabled",
"twitter_enabled", "osx_notify_enabled",
"boxcar_enabled", "email_enabled", "email_tls",
"grouping_global_history", "grouping_user_history", "grouping_charts", "pms_use_bif", "pms_ssl", "grouping_global_history", "grouping_user_history", "grouping_charts", "pms_use_bif", "pms_ssl",
"tv_notify_enable", "movie_notify_enable", "music_notify_enable", "tv_notify_enable", "movie_notify_enable", "music_notify_enable",
"tv_notify_on_start", "movie_notify_on_start", "music_notify_on_start", "tv_notify_on_start", "movie_notify_on_start", "music_notify_on_start",
@ -515,6 +502,14 @@ class WebInterface(object):
if kwargs['pms_ip'] != plexpy.CONFIG.PMS_IP: if kwargs['pms_ip'] != plexpy.CONFIG.PMS_IP:
refresh_users = True refresh_users = True
if 'home_stats_cards' in kwargs:
if kwargs['home_stats_cards'] != 'watch_statistics':
kwargs['home_stats_cards'] = ', '.join(kwargs['home_stats_cards'])
if 'home_library_cards' in kwargs:
if kwargs['home_library_cards'] != 'library_statistics':
kwargs['home_library_cards'] = ', '.join(kwargs['home_library_cards'])
plexpy.CONFIG.process_kwargs(kwargs) plexpy.CONFIG.process_kwargs(kwargs)
# Write the config # Write the config
@ -535,15 +530,6 @@ class WebInterface(object):
@cherrypy.expose @cherrypy.expose
def set_notification_config(self, **kwargs): def set_notification_config(self, **kwargs):
# Handle the variable config options. Note - keys with False values aren't getting passed
checked_configs = [
"email_tls"
]
for checked_config in checked_configs:
if checked_config not in kwargs:
# checked items should be zero or one. if they were not sent then the item was not checked
kwargs[checked_config] = 0
for plain_config, use_config in [(x[4:], x) for x in kwargs if x.startswith('use_')]: for plain_config, use_config in [(x[4:], x) for x in kwargs if x.startswith('use_')]:
# the use prefix is fairly nice in the html, but does not match the actual config # the use prefix is fairly nice in the html, but does not match the actual config
@ -1112,6 +1098,18 @@ class WebInterface(object):
else: else:
logger.warn('Unable to retrieve data.') logger.warn('Unable to retrieve data.')
@cherrypy.expose
def get_server_children(self, **kwargs):
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_server_children()
if result:
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps(result)
else:
logger.warn('Unable to retrieve data.')
@cherrypy.expose @cherrypy.expose
def get_activity(self, **kwargs): def get_activity(self, **kwargs):