Merge branch 'dev'

This commit is contained in:
JonnyWong16 2016-03-12 15:04:40 -08:00
commit 08537c1d69
50 changed files with 1904 additions and 784 deletions

View file

@ -1,9 +1,39 @@
# Changelog # Changelog
## v1.3.10 (2016-03-12)
* Fix: Actually allow HTML tags for Pushover.
* Fix: PlexPy not restarting on Windows if there is a space in the folder path.
* Fix: Reconnect websocket when changing PMS SSL setting.
* Fix: Datatables not loading when view_offset or duration is blank.
* Fix: Bug when checking the PMS version in the settings.
* Fix: Auto-refreshing of log tables.
* Fix: Logging of IPv6 addresses. (PMS version >0.9.14 only.)
* Fix: Hide days selection from the Play Totals graph page.
* Fix: PlexPy overwriting user's own SSL certificate/key.
* Fix: Multiple watched notifications when using websocket.
* Fix: Some missing python library imports.
* Fix: Some typos in settings and PlexWatch importer.
* New: Ability to get notified of PMS updates.
* New: Ability to disable the link to Plex Web with Facebook notifications and use IMDB, TVDB, TMDb, or Last.fm instead.
* New: Ability to reset Imgur poster url from the info page if the poster is changed.
* New: Tooltips on the current activity progress bars.
* New: Side scrolling of Recently Added/Recently Played items.
* New: Document all date/time format options.
* New: Button to clear notification logs.
* New: Customizable backup, cache, and log directories.
* Change: Retry writing sessions to history if it fails, so sessions don't get lost. (Activity pinger only, not availble for websocket.)
* Change: Save any unknown sessions to the "Local" user.
* Change: History table modal is filtered depending on which graph series is clicked.
* Change: Revert back to saving the state of datatables (search, sorting, entries per page, etc.).
* Change: Newlines are not longer stripped from notification text which allows for finer control of how notifications look.
* Change: Updated FreeNAS/FreeBSD init scripts. (Must have updated jails.) (Thanks @chiviak)
## v1.3.9 (2016-02-21) ## v1.3.9 (2016-02-21)
* Fix: Recently added notification not sent to all notification agents. * Fix: Recently added notification not sent to all notification agents.
* Add: Pushover HTML support. (Thanks @elseym) * New: Pushover HTML support. (Thanks @elseym)
## v1.3.8 (2016-02-21) ## v1.3.8 (2016-02-21)
@ -12,8 +42,8 @@
* Fix: Remove media tags from script arguments for server notifications. * Fix: Remove media tags from script arguments for server notifications.
* Fix: Encode poster titles to UTF-8 for Imgur upload. * Fix: Encode poster titles to UTF-8 for Imgur upload.
* Fix: Allow notifications to send without poster if Imgur upload fails. * Fix: Allow notifications to send without poster if Imgur upload fails.
* Add: Notification Logs table in the Logs tab. * New: Notification Logs table in the Logs tab.
* Add: Toggle in settings to enable posters in notifications. (Disabled by default.) * New: Toggle in settings to enable posters in notifications. (Disabled by default.)
* Change: Save Imgur poster URL to database so upload is not needed every time. * Change: Save Imgur poster URL to database so upload is not needed every time.
* Change: Notify log in database to log each event as a separate entry. * Change: Notify log in database to log each event as a separate entry.
* Change: Monitor remote access is unchecked if remote access is disabled on server. * Change: Monitor remote access is unchecked if remote access is disabled on server.
@ -26,18 +56,18 @@
* Fix: Video metadata flags showing up for track info. * Fix: Video metadata flags showing up for track info.
* Fix: Custom library icons not applied to Library Statistics. * Fix: Custom library icons not applied to Library Statistics.
* Fix: Typos in the Web UI. * Fix: Typos in the Web UI.
* Add: ETA to Current Activity overlay. * New: ETA to Current Activity overlay.
* Add: Total duration to Libraries and Users tables. * New: Total duration to Libraries and Users tables.
* Add: {machine_id} to notification options. * New: {machine_id} to notification options.
* Add: IMDB, TVDB, TMDb, Last.fm, and Trackt IDs/URLs to notification options. * New: IMDB, TVDB, TMDb, Last.fm, and Trackt IDs/URLs to notification options.
* Add: {poster_url} to notification options using Imgur. * New: {poster_url} to notification options using Imgur.
* Add: Poster and link for Facebook notifications. * New: Poster and link for Facebook notifications.
* Add: Log javascript errors from the Web UI. * New: Log javascript errors from the Web UI.
* Add: Configuration and Scheduler info to the settings page. * New: Configuration and Scheduler info to the settings page.
* Add: Schedule background task to backup the PlexPy database. * New: Schedule background task to backup the PlexPy database.
* Add: URL anonymizer for external links. * New: URL anonymizer for external links.
* Add: Plex Media Scanner log file to Log viewer. * New: Plex Media Scanner log file to Log viewer.
* Add: API v2 (sill very experimental). (Thanks @Hellowlol) * New: API v2 (sill very experimental). (Thanks @Hellowlol)
* Change: Allow secure websocket connections. * Change: Allow secure websocket connections.
* Change: History grouping now accounts for the view offset. * Change: History grouping now accounts for the view offset.
* Change: Subject line can be toggled off for Facebook, Slack, Telegram, and Twitter. * Change: Subject line can be toggled off for Facebook, Slack, Telegram, and Twitter.
@ -60,9 +90,9 @@
* Fix: Unable to expand media info table when missing "Added At" date. * Fix: Unable to expand media info table when missing "Added At" date.
* Fix: Server verification for unpublished servers. * Fix: Server verification for unpublished servers.
* Fix: Updating PMS identifier for server change. * Fix: Updating PMS identifier for server change.
* Add: {stream_time}, {remaining_time}, and {progress_time} to notification options. * New: {stream_time}, {remaining_time}, and {progress_time} to notification options.
* Add: Powershell script support. (Thanks @Hellowlol) * New: Powershell script support. (Thanks @Hellowlol)
* Add: Method to delete duplicate libraries. * New: Method to delete duplicate libraries.
* Change: Daemonize before running start up tasks. * Change: Daemonize before running start up tasks.
@ -74,7 +104,7 @@
* Fix: Libraries and Users lists not refreshing. * Fix: Libraries and Users lists not refreshing.
* Fix: Server verification in settings. * Fix: Server verification in settings.
* Fix: Empty libraries not added to database. * Fix: Empty libraries not added to database.
* Add: Unique identifiers to notification options. * New: Unique identifiers to notification options.
* Remove: Requirement of media type toggles for recently added notifications. * Remove: Requirement of media type toggles for recently added notifications.
* Remove: Built in Twitter key and secret. * Remove: Built in Twitter key and secret.
* Change: Unnecessary quoting of script arguments. * Change: Unnecessary quoting of script arguments.
@ -107,21 +137,21 @@
## v1.3.0 (2016-01-23) ## v1.3.0 (2016-01-23)
* Add: Brand new Libraries section. * New: Brand new Libraries section.
* Add: Lots of new library statistics. * New: Lots of new library statistics.
* Add: Media info table for libraries. * New: Media info table for libraries.
* Add: Web app for Android and iOS. (Thanks @zobe123) * New: Web app for Android and iOS. (Thanks @zobe123)
* Add: Slack notification agent. (Thanks @richipargo) * New: Slack notification agent. (Thanks @richipargo)
* Add: Facebook notification agent. * New: Facebook notification agent.
* Add: Custom script notification agent. (Thanks @Hellowlol) * New: Custom script notification agent. (Thanks @Hellowlol)
* Add: Custom "From Name" to email notification agent. * New: Custom "From Name" to email notification agent.
* Add: Ability to test notifications / send custom one-off notifications. * New: Ability to test notifications / send custom one-off notifications.
* Add: 'datestamp' and 'timestamp' notification options. * New: 'datestamp' and 'timestamp' notification options.
* Add: More concurrent stream statistics. * New: More concurrent stream statistics.
* Add: Media info flags on the info pages. * New: Media info flags on the info pages.
* Add: Ability to fix broken metadata if the item has been moved in Plex. * New: Ability to fix broken metadata if the item has been moved in Plex.
* Add: Ability to rearrange the homepage statistics cards. * New: Ability to rearrange the homepage statistics cards.
* Add: CentOS startup script (Thanks @PHoSawyer) * New: CentOS startup script (Thanks @PHoSawyer)
* Fix: Server name blank after first run wizard. * Fix: Server name blank after first run wizard.
* Fix: Incorrect duration for grouped home stats. * Fix: Incorrect duration for grouped home stats.
* Fix: Allow SSL when verifying server in settings. * Fix: Allow SSL when verifying server in settings.

View file

@ -1,6 +1,7 @@
<% <%
import plexpy import plexpy
from plexpy import version from plexpy import version
from plexpy.helpers import anon_url
%> %>
<!doctype html> <!doctype html>
@ -136,15 +137,15 @@ from plexpy import version
<div id="ajaxMsg" class="ajaxMsg"></div> <div id="ajaxMsg" class="ajaxMsg"></div>
% if plexpy.CONFIG.CHECK_GITHUB and not plexpy.CURRENT_VERSION: % if plexpy.CONFIG.CHECK_GITHUB and not plexpy.CURRENT_VERSION:
<div id="updatebar" style="display: none;"> <div id="updatebar" style="display: none;">
You're running an unknown version of PlexPy. <a href="update">Update</a> or You're running an unknown version of PlexPy.<br />
<a href="#" id="updateDismiss">Close</a> <a href="update">Update</a> or <a href="#" id="updateDismiss">Close</a>
</div> </div>
% elif plexpy.CONFIG.CHECK_GITHUB and plexpy.CURRENT_VERSION != plexpy.LATEST_VERSION and plexpy.COMMITS_BEHIND > 0 and plexpy.INSTALL_TYPE != 'win': % elif plexpy.CONFIG.CHECK_GITHUB and plexpy.CURRENT_VERSION != plexpy.LATEST_VERSION and plexpy.COMMITS_BEHIND > 0 and plexpy.INSTALL_TYPE != 'win':
<div id="updatebar" style="display: none;"> <div id="updatebar" style="display: none;">
A <a A <a href="${anon_url('https://github.com/%s/plexpy/compare/%s...%s' % (plexpy.CONFIG.GIT_USER, plexpy.CURRENT_VERSION, plexpy.LATEST_VERSION))}" target="_blank">
href="https://github.com/${plexpy.CONFIG.GIT_USER}/plexpy/compare/${plexpy.CURRENT_VERSION}...${plexpy.LATEST_VERSION}" target="_blank"> newer version</a> is available.<br />
newer version</a> is available. You're ${plexpy.COMMITS_BEHIND} commits behind. <a href="update">Update</a> or You're ${plexpy.COMMITS_BEHIND} commits behind.<br />
<a href="#" id="updateDismiss">Close</a> <a href="update">Update</a> or <a href="#" id="updateDismiss">Close</a>
</div> </div>
% endif % endif
<nav class="navbar navbar-fixed-top"> <nav class="navbar navbar-fixed-top">

View file

@ -882,10 +882,11 @@ a .dashboard-activity-metadata-user-thumb:hover {
} }
.dashboard-recent-media-row { .dashboard-recent-media-row {
width: 100%; width: 100%;
margin:0 auto; margin: 0 auto;
text-align: center; text-align: center;
position: relative; position: relative;
z-index: 0; z-index: 0;
overflow: hidden;
} }
.dashboard-recent-media { .dashboard-recent-media {
width: 100%; width: 100%;
@ -2407,6 +2408,9 @@ a .home-platforms-instance-list-oval:hover,
padding: 0; padding: 0;
border: 0; border: 0;
} }
.history-thumbnail-popover.popover.left {
margin-left: -15px;
}
.history-thumbnail-popover.popover.right { .history-thumbnail-popover.popover.right {
margin-left: 15px; margin-left: 15px;
} }
@ -2655,6 +2659,7 @@ table[id^='media_info_child'] table[id^='media_info_child'] thead th {
margin: 5px 0 0 0.5em; margin: 5px 0 0 0.5em;
} }
.notification-params { .notification-params {
width: 100%;
margin-top: 10px; margin-top: 10px;
background-color: #282828; background-color: #282828;
} }
@ -2669,6 +2674,14 @@ table[id^='media_info_child'] table[id^='media_info_child'] thead th {
padding-left: 10px; padding-left: 10px;
width: 200px; width: 200px;
} }
.notification-params.time-options td:first-child {
padding-left: 10px;
width: 125px;
}
.notification-params.time-options td:nth-child(2) {
padding-left: 10px;
width: 275px;
}
.notification-params td:not(:first-child) { .notification-params td:not(:first-child) {
padding-right: 10px; padding-right: 10px;
} }
@ -2759,3 +2772,21 @@ a.no-highlight:hover {
.save-button { .save-button {
margin-top: 15px; margin-top: 15px;
} }
.nav-dashboard > li {
float: left;
}
.btn-gray.disabled,
.btn-gray.disabled:focus,
.btn-gray.disabled:hover,
.btn-gray.disabled:active {
color: #323232;
cursor: default;
}
.nav-header > li > a:focus,
.nav-header > li > a:hover {
background-color: transparent;
}
#recently-added-row-scroller,
#recently-watched-row-scroller {
position: relative;
}

View file

@ -217,8 +217,8 @@ DOCUMENTATION :: END
% endif % endif
<div class="dashboard-activity-progress"> <div class="dashboard-activity-progress">
<div class="dashboard-activity-progress-bar"> <div class="dashboard-activity-progress-bar">
<div class="bufferbar" style="width: ${a['transcode_progress']}%">${a['transcode_progress']}%</div> <div class="bufferbar" style="width: ${a['transcode_progress']}%" data-toggle="tooltip" title="Transcoder Progress">${a['transcode_progress']}%</div>
<div class="bar" style="width: ${a['progress_percent']}%">${a['progress_percent']}%</div> <div class="bar" style="width: ${a['progress_percent']}%" data-toggle="tooltip" title="Stream Progress">${a['progress_percent']}%</div>
</div> </div>
</div> </div>
<div class="dashboard-activity-metadata-wrapper"> <div class="dashboard-activity-metadata-wrapper">
@ -261,11 +261,7 @@ DOCUMENTATION :: END
% endif % endif
</div> </div>
<div class="dashboard-activity-metadata-user"> <div class="dashboard-activity-metadata-user">
% if a['user_id']:
<a href="user?user_id=${a['user_id']}" title="${a['friendly_name']}">${a['friendly_name']}</a> <a href="user?user_id=${a['user_id']}" title="${a['friendly_name']}">${a['friendly_name']}</a>
% else:
<a href="user?user=${a['user']}" title="${a['friendly_name']}">${a['friendly_name']}</a>
% endif
</div> </div>
</div> </div>
</div> </div>
@ -289,11 +285,13 @@ DOCUMENTATION :: END
}); });
// Add hover class to dashboard-instance // Add hover class to dashboard-instance
$('.dashboard-activity-poster').hover(function() { $('.dashboard-activity-poster, .dashboard-activity-progress-bar').hover(function() {
$(this).closest('.dashboard-instance').addClass('hover'); $(this).closest('.dashboard-instance').addClass('hover');
}, function() { }, function() {
$(this).closest('.dashboard-instance').removeClass('hover'); $(this).closest('.dashboard-instance').removeClass('hover');
}); });
$('.bar, .bufferbar').tooltip({container: 'body', placement: 'right', delay: 50});
</script> </script>
% else: % else:
<div class="text-muted">Nothing is currently being watched.</div><br> <div class="text-muted">Nothing is currently being watched.</div><br>

View file

@ -249,7 +249,7 @@
<script> <script>
// Modal popup dialog // Modal popup dialog
function selectHandler(selectedDate) { function selectHandler(selectedDate, selectedSeries) {
try try
{ {
@ -259,10 +259,25 @@
var y = dateValue.getFullYear(); var y = dateValue.getFullYear();
var dateString = '' + y + '-' + (m<=9 ? '0' + m : m) + '-' + (d <= 9 ? '0' + d : d); var dateString = '' + y + '-' + (m<=9 ? '0' + m : m) + '-' + (d <= 9 ? '0' + d : d);
var media_type = 'all';
var transcode_decision = null;
switch(selectedSeries) {
case "TV": media_type = 'episode'; break;
case "Movies": media_type = 'movie'; break;
case "Music": media_type = 'track'; break;
case "Direct Play": transcode_decision = 'direct play'; break;
case "Direct Stream": transcode_decision = 'copy'; break;
case "Transcode": transcode_decision = 'transcode'; break;
}
$.ajax({ $.ajax({
"url": "history_table_modal", url: "history_table_modal",
type: 'post', type: 'post',
data: { 'start_date': dateString }, data: {
start_date: dateString,
media_type: media_type,
transcode_decision: transcode_decision
},
complete: function(xhr, status) { complete: function(xhr, status) {
$('#history-modal').modal('show'); $('#history-modal').modal('show');
$("#history-modal").html(xhr.responseText); $("#history-modal").html(xhr.responseText);
@ -271,7 +286,7 @@
} }
catch(err) catch(err)
{ {
console.log("Failed to retrieve data"); console.log("Failed to retrieve history modal data.");
} }
} }
</script> </script>
@ -299,6 +314,8 @@
var music_visible = (${config['music_logging_enable']} == 1 ? true : false); var music_visible = (${config['music_logging_enable']} == 1 ? true : false);
function loadGraphsTab1(time_range, yaxis) { function loadGraphsTab1(time_range, yaxis) {
$('#days-selection').show();
setGraphFormat(yaxis); setGraphFormat(yaxis);
$.ajax({ $.ajax({
@ -382,6 +399,8 @@
} }
function loadGraphsTab2(time_range, yaxis) { function loadGraphsTab2(time_range, yaxis) {
$('#days-selection').show();
setGraphFormat(yaxis); setGraphFormat(yaxis);
$.ajax({ $.ajax({
@ -460,6 +479,8 @@
} }
function loadGraphsTab3(yaxis) { function loadGraphsTab3(yaxis) {
$('#days-selection').hide();
setGraphFormat(yaxis); setGraphFormat(yaxis);
$.ajax({ $.ajax({

View file

@ -22,7 +22,7 @@
</div> </div>
</div> </div>
<div class='table-card-back'> <div class='table-card-back'>
<table class="display" id="history_table" width="100%"> <table class="display history_table" id="history_table" width="100%">
<thead> <thead>
<tr> <tr>
<th align="left" id="delete_row">Delete</th> <th align="left" id="delete_row">Delete</th>

View file

@ -5,12 +5,12 @@
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
<h4 class="modal-title" id="myModalLabel"> <h4 class="modal-title" id="myModalLabel">
<strong><span id="modal_header_ip_address"> <strong><span id="modal_header_ip_address">
<i class="fa fa-history"></i> History for <span id="date-header">${data}</span> <i class="fa fa-history"></i> History for <span id="date-header">${data['start_date']}</span>
</span></strong> </span></strong>
</h4> </h4>
</div> </div>
<div class="modal-body" id="modal-text"> <div class="modal-body" id="modal-text">
<table class="display" id="history_table" width="100%"> <table class="display history_table" id="history_table_modal" width="100%">
<thead> <thead>
<tr> <tr>
<th align="left" id="started">Started</th> <th align="left" id="started">Started</th>
@ -32,7 +32,7 @@
<script src="interfaces/default/js/tables/history_table_modal.js"></script> <script src="interfaces/default/js/tables/history_table_modal.js"></script>
<script> <script>
$(document).ready(function() { $(document).ready(function() {
$('#date-header').html(moment('${data}','YYYY-MM-DD').format('ddd MMM Do YYYY')); $('#date-header').html(moment('${data["start_date"]}','YYYY-MM-DD').format('ddd MMM Do YYYY'));
history_table_modal_options.ajax = { history_table_modal_options.ajax = {
url: 'get_history', url: 'get_history',
type: 'post', type: 'post',
@ -40,14 +40,16 @@
return { return {
json_data: JSON.stringify(d), json_data: JSON.stringify(d),
grouping: false, grouping: false,
start_date: '${data}' start_date: "${data['start_date']}",
}; media_type: "${data.get('media_type')}",
transcode_decision: "${data.get('transcode_decision')}"
};
} }
} }
history_table = $('#history_table').DataTable(history_table_modal_options); history_table = $('#history_table_modal').DataTable(history_table_modal_options);
clearSearchButton('history_table', history_table); clearSearchButton('history_table_modal', history_table);
// Move #info-modal to parent container // Move #info-modal to parent container
if (!($('#history-modal').next().is('#info-modal'))) { if (!($('#history-modal').next().is('#info-modal'))) {

View file

@ -23,7 +23,7 @@
<h3>Watch Statistics <small>Last ${config['home_stats_length']} days</small></h3> <h3>Watch Statistics <small>Last ${config['home_stats_length']} days</small></h3>
</div> </div>
<div id="home-stats" class="home-platforms"> <div id="home-stats" class="home-platforms">
<div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Loading stats...</div> <div class="text-muted"><i class="fa fa-refresh fa-spin"></i> Loading stats...</div>
<br> <br>
</div> </div>
</div> </div>
@ -36,19 +36,27 @@
<h3>Library Statistics <small>${config['pms_name']}</small></h3> <h3>Library Statistics <small>${config['pms_name']}</small></h3>
</div> </div>
<div id="library-stats" class="library-platforms"> <div id="library-stats" class="library-platforms">
<div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Loading stats...</div> <div class="text-muted"><i class="fa fa-refresh fa-spin"></i> Loading stats...</div>
<br> <br>
</div> </div>
</div> </div>
</div> </div>
% endif % 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">
<ul class="nav nav-header nav-dashboard pull-right">
<li>
<a href="#" id="recently-added-page-left" class="paginate btn-gray disabled" data-id="+1"><i class="fa fa-lg fa-chevron-left"></i></a>
</li>
<li>
<a href="#" id="recently-added-page-right" class="paginate btn-gray disabled" data-id="-1"><i class="fa fa-lg fa-chevron-right"></i></a>
</li>
</ul>
<h3>Recently Added</h3> <h3>Recently Added</h3>
</div> </div>
<div id='recentlyAdded'> <div id="recentlyAdded" style="margin-right: -15px;">
<div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Looking for new items...</div> <div class="text-muted"><i class="fa fa-refresh fa-spin"></i> Looking for new items...</div>
<br> <br>
</div> </div>
</div> </div>
@ -59,7 +67,6 @@
<%def name="javascriptIncludes()"> <%def name="javascriptIncludes()">
<script src="interfaces/default/js/moment-with-locale.js"></script> <script src="interfaces/default/js/moment-with-locale.js"></script>
<script> <script>
function currentActivityHeader() { function currentActivityHeader() {
$.ajax({ $.ajax({
url: 'get_current_activity_header', url: 'get_current_activity_header',
@ -71,7 +78,6 @@
}); });
} }
currentActivityHeader(); currentActivityHeader();
setInterval(currentActivityHeader, 15000);
function currentActivity() { function currentActivity() {
$.ajax({ $.ajax({
@ -84,7 +90,12 @@
}); });
} }
currentActivity(); currentActivity();
setInterval(currentActivity, 15000);
setInterval(function () {
$('.bar, .bufferbar').tooltip('destroy');
currentActivityHeader();
currentActivity();
}, 15000);
function getHomeStats(days) { function getHomeStats(days) {
$.ajax({ $.ajax({
@ -97,6 +108,7 @@
} }
}); });
} }
getHomeStats();
function getLibraryStats() { function getLibraryStats() {
$.ajax({ $.ajax({
@ -109,33 +121,21 @@
} }
}); });
} }
getLibraryStats();
function recentlyAdded() { function recentlyAdded() {
var widthVal = $('body').find(".container-fluid").width();
var tmp = (widthVal-20) / 182;
if (tmp > 0) {
containerSize = parseInt(tmp);
} else {
containerSize = 1;
}
$.ajax({ $.ajax({
url: 'get_recently_added', url: 'get_recently_added',
type: "GET", type: "GET",
async: true, async: true,
data: { count : containerSize }, data: { count : 50 },
complete: function(xhr, status) { complete: function(xhr, status) {
$("#recentlyAdded").html(xhr.responseText); $("#recentlyAdded").html(xhr.responseText);
highlightAddedScrollerButton();
} }
}); });
} }
$(document).ready(function () { recentlyAdded();
recentlyAdded();
$(window).resize(function() {
recentlyAdded();
});
});
var date_format = 'YYYY-MM-DD'; var date_format = 'YYYY-MM-DD';
var time_format = 'hh:mm a'; var time_format = 'hh:mm a';
@ -148,10 +148,44 @@
} }
}); });
getHomeStats(); function highlightAddedScrollerButton() {
getLibraryStats(); var scroller = $("#recently-added-row-scroller");
var numElems = scroller.find("li").length;
scroller.width(numElems * 175);
if (scroller.width() > $("body").find(".container-fluid").width()) {
$("#recently-added-page-right").removeClass("disabled");
} else {
$("#recently-added-page-right").addClass("disabled");
}
}
$(window).resize(function () {
highlightAddedScrollerButton();
});
var leftTotal = 0;
$(".paginate").click(function (e) {
e.preventDefault();
var scroller = $("#recently-added-row-scroller");
var containerWidth = $("body").find(".container-fluid").width();
var scrollAmount = $(this).data("id") * parseInt((containerWidth - 15) / 175) * 175;
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
leftTotal = Math.max(Math.min(leftTotal + scrollAmount, 0), leftMax);
scroller.animate({ left: leftTotal }, 250);
if (leftTotal == 0) {
$("#recently-added-page-left").addClass("disabled").blur();
} else {
$("#recently-added-page-left").removeClass("disabled");
}
if (leftTotal == leftMax) {
$("#recently-added-page-right").addClass("disabled").blur();
} else {
$("#recently-added-page-right").removeClass("disabled");
}
});
</script> </script>
</%def> </%def>

View file

@ -344,25 +344,41 @@ DOCUMENTATION :: END
<button class="btn btn-danger btn-edit" data-toggle="button" aria-pressed="false" autocomplete="off" id="row-edit-mode"> <button class="btn btn-danger btn-edit" data-toggle="button" aria-pressed="false" autocomplete="off" id="row-edit-mode">
<i class="fa fa-trash-o"></i> Delete mode <i class="fa fa-trash-o"></i> Delete mode
</button> </button>
% if source == 'history':
<a href="update_metadata?rating_key=${data['rating_key']}&update=True" class="btn btn-danger btn-edit" id="fix-metadata">
<i class="fa fa-wrench"></i> Fix Metadata
</a>
% endif
% if data.get('poster_url'):
% if data['media_type'] == 'artist' or data['media_type'] == 'album' or data['media_type'] == 'track':
<span class="imgur-poster-tooltip" data-toggle="popover" data-img="${data['poster_url']}" data-height="80" data-width="80" style="display: inline-flex;">
% else:
<span class="imgur-poster-tooltip" data-toggle="popover" data-img="${data['poster_url']}" data-height="120" data-width="80" style="display: inline-flex;">
% endif
<button class="btn btn-danger btn-edit" data-toggle="button" aria-pressed="false" autocomplete="off" id="delete-imgur-poster">
<i class="fa fa-picture-o"></i> Reset Imgur Poster
</button>
</span>
% endif
<div class="alert alert-danger alert-edit" role="alert" id="row-edit-mode-alert"><i class="fa fa-exclamation-triangle"></i>&nbspSelect rows to delete. Data is deleted upon exiting delete mode.</div> <div class="alert alert-danger alert-edit" role="alert" id="row-edit-mode-alert"><i class="fa fa-exclamation-triangle"></i>&nbspSelect rows to delete. Data is deleted upon exiting delete mode.</div>
</div> </div>
</div> </div>
<div class="table-card-back"> <div class="table-card-back">
<table class="display" id="history_table" width="100%"> <table class="display history_table" id="history_table-RK-${data['rating_key']}" width="100%">
<thead> <thead>
<tr> <tr>
<th align='left' id="delete">Delete</th> <th align="left" id="delete">Delete</th>
<th align='left' id="time">Time</th> <th align="left" id="time">Time</th>
<th align='left' id="friendly_name">User</th> <th align="left" id="friendly_name">User</th>
<th align='left' id="ip_address">IP Address</th> <th align="left" id="ip_address">IP Address</th>
<th align='left' id="platform">Platform</th> <th align="left" id="platform">Platform</th>
<th align='left' id="player">Player</th> <th align="left" id="player">Player</th>
<th align='left' id="title">Title</th> <th align="left" id="title">Title</th>
<th align='left' id="started">Started</th> <th align="left" id="started">Started</th>
<th align='left' id="paused_counter">Paused</th> <th align="left" id="paused_counter">Paused</th>
<th align='left' id="stopped">Stopped</th> <th align="left" id="stopped">Stopped</th>
<th align='left' id="duration">Duration</th> <th align="left" id="duration">Duration</th>
<th align='left' id="percent_complete"></th> <th align="left" id="percent_complete"></th>
</tr> </tr>
</thead> </thead>
<tbody></tbody> <tbody></tbody>
@ -458,11 +474,11 @@ DOCUMENTATION :: END
<script> <script>
$(document).ready(function () { $(document).ready(function () {
get_history(); get_history();
history_table = $('#history_table').DataTable(history_table_options); history_table = $('#history_table-RK-${data["rating_key"]}').DataTable(history_table_options);
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] }); var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] });
$(colvis.button()).appendTo('div.colvis-button-bar'); $(colvis.button()).appendTo('div.colvis-button-bar');
clearSearchButton('history_table', history_table); clearSearchButton('history_table-RK-${data["rating_key"]}', history_table);
$('#row-edit-mode').on('click', function() { $('#row-edit-mode').on('click', function() {
$('#row-edit-mode-alert').fadeIn(200); $('#row-edit-mode-alert').fadeIn(200);
@ -519,10 +535,33 @@ DOCUMENTATION :: END
$("#runtime").html(millisecondsToMinutes($("#runtime").text(), true)); $("#runtime").html(millisecondsToMinutes($("#runtime").text(), true));
$('div.art-face').animate({ opacity: 0.2 }, { duration: 1000 }); $('div.art-face').animate({ opacity: 0.2 }, { duration: 1000 });
</script> </script>
% if source == 'history': % if data.get('poster_url'):
<script> <script>
$('#row-edit-mode').after('<a href="update_metadata?rating_key=${data['rating_key']}&update=True" class="btn btn-danger btn-edit" id="fix-metadata"> \ $('.imgur-poster-tooltip').popover({
<i class="fa fa-wrench"></i> Fix Metadata</a>'); html: true,
container: 'body',
trigger: 'hover',
placement: 'left',
template: '<div class="popover history-thumbnail-popover" role="tooltip"><div class="arrow" style="top: 50%;"></div><div class="popover-content"></div></div>',
content: function () {
return '<div class="history-thumbnail" style="background-image: url(' + $(this).data('img') + '); height: ' + $(this).data('height') + 'px; width: ' + $(this).data('width') + 'px;" />';
}
});
$('#delete-imgur-poster').on('click', function() {
$.ajax({
url: 'delete_poster_url',
type: 'POST',
async: true,
data: { poster_url : "${data['poster_url']}" },
success: function (data) {
var msg = '<i class="fa fa-check"></i>&nbsp; Imgur poster reset';
showMsg(msg, false, true, 2000);
$('.imgur-poster-tooltip').popover('destroy');
$('#delete-imgur-poster').closest('span').remove();
}
});
});
</script> </script>
% endif % endif
% endif % endif

View file

@ -32,7 +32,8 @@
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<span class="text-muted">Telize service written by <a href="https://github.com/fcambus/telize" target="_blank">Frederic Cambus</a>.</span> <% from plexpy.helpers import anon_url %>
<span class="text-muted">Telize service written by <a href="${anon_url('https://github.com/fcambus/telize')}" target="_blank">Frederic Cambus</a>.</span>
</div> </div>
</div> </div>
</div> </div>

View file

@ -28,8 +28,8 @@ var hc_plays_by_day_options = {
cursor: 'pointer', cursor: 'pointer',
point: { point: {
events: { events: {
click: function() { click: function () {
selectHandler(this.category); selectHandler(this.category, this.series.name);
} }
} }
} }
@ -60,7 +60,8 @@ var hc_plays_by_day_options = {
} }
}, },
tooltip: { tooltip: {
shared: true shared: true,
crosshairs: true
}, },
series: [{}] series: [{}]
}; };

View file

@ -29,7 +29,7 @@ var hc_plays_by_stream_type_options = {
point: { point: {
events: { events: {
click: function() { click: function() {
selectHandler(this.category); selectHandler(this.category, this.series.name);
} }
} }
} }
@ -60,7 +60,8 @@ var hc_plays_by_stream_type_options = {
} }
}, },
tooltip: { tooltip: {
shared: true shared: true,
crosshairs: true
}, },
series: [{}] series: [{}]
}; };

View file

@ -22,7 +22,7 @@ history_table_options = {
"emptyTable": "No data in table" "emptyTable": "No data in table"
}, },
"pagingType": "bootstrap", "pagingType": "bootstrap",
"stateSave": false, "stateSave": true,
"processing": false, "processing": false,
"serverSide": true, "serverSide": true,
"pageLength": 25, "pageLength": 25,
@ -107,7 +107,7 @@ history_table_options = {
} }
}, },
"width": "10%", "width": "10%",
"className": "no-wrap hidden-md hidden-sm hidden-xs modal-control" "className": "no-wrap hidden-md hidden-sm hidden-xs"
}, },
{ {
"targets": [5], "targets": [5],
@ -115,11 +115,11 @@ history_table_options = {
"createdCell": function (td, cellData, rowData, row, col) { "createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') { if (cellData !== '') {
var transcode_dec = ''; var transcode_dec = '';
if (rowData['video_decision'] === 'transcode' || rowData['audio_decision'] === 'transcode') { if (rowData['transcode_decision'] === 'transcode') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Transcode"><i class="fa fa-server fa-fw"></i></span>'; transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Transcode"><i class="fa fa-server fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'copy' || rowData['audio_decision'] === 'copy') { } else if (rowData['transcode_decision'] === 'copy') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-video-camera fa-fw"></i></span>'; transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-video-camera fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'direct play' || rowData['audio_decision'] === 'direct play') { } else if (rowData['transcode_decision'] === 'direct play') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Play"><i class="fa fa-play-circle fa-fw"></i></span>'; transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Play"><i class="fa fa-play-circle fa-fw"></i></span>';
} }
$(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + '&nbsp;' + cellData + '</div></a></div>'); $(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + '&nbsp;' + cellData + '</div></a></div>');
@ -307,7 +307,7 @@ history_table_options = {
} }
// Parent table platform modal // Parent table platform modal
$('#history_table').on('click', '> tbody > tr > td.modal-control', function () { $('.history_table').on('click', '> tbody > tr > td.modal-control', function () {
var tr = $(this).closest('tr'); var tr = $(this).closest('tr');
var row = history_table.row( tr ); var row = history_table.row( tr );
var rowData = row.data(); var rowData = row.data();
@ -327,7 +327,7 @@ $('#history_table').on('click', '> tbody > tr > td.modal-control', function () {
}); });
// Parent table ip address modal // Parent table ip address modal
$('#history_table').on('click', '> tbody > tr > td.modal-control-ip', function () { $('.history_table').on('click', '> tbody > tr > td.modal-control-ip', function () {
var tr = $(this).closest('tr'); var tr = $(this).closest('tr');
var row = history_table.row( tr ); var row = history_table.row( tr );
var rowData = row.data(); var rowData = row.data();
@ -350,7 +350,7 @@ $('#history_table').on('click', '> tbody > tr > td.modal-control-ip', function (
}); });
// Parent table delete mode // Parent table delete mode
$('#history_table').on('click', '> tbody > tr > td.delete-control > button', function () { $('.history_table').on('click', '> tbody > tr > td.delete-control > button', function () {
var tr = $(this).closest('tr'); var tr = $(this).closest('tr');
var row = history_table.row( tr ); var row = history_table.row( tr );
var rowData = row.data(); var rowData = row.data();
@ -399,7 +399,7 @@ $('#history_table').on('click', '> tbody > tr > td.delete-control > button', fun
}); });
// Parent table expand detailed history // Parent table expand detailed history
$('#history_table').on('click', '> tbody > tr > td.expand-history a', function () { $('.history_table').on('click', '> tbody > tr > td.expand-history a', function () {
var tr = $(this).closest('tr'); var tr = $(this).closest('tr');
var row = history_table.row(tr); var row = history_table.row(tr);
var rowData = row.data(); var rowData = row.data();

View file

@ -79,11 +79,11 @@ history_table_modal_options = {
"createdCell": function (td, cellData, rowData, row, col) { "createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') { if (cellData !== '') {
var transcode_dec = ''; var transcode_dec = '';
if (rowData['video_decision'] === 'transcode' || rowData['audio_decision'] === 'transcode') { if (rowData['transcode_decision'] === 'transcode') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Transcode"><i class="fa fa-server fa-fw"></i></span>'; transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Transcode"><i class="fa fa-server fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'copy' || rowData['audio_decision'] === 'copy') { } else if (rowData['transcode_decision'] === 'copy') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-video-camera fa-fw"></i></span>'; transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-video-camera fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'direct play' || rowData['audio_decision'] === 'direct play') { } else if (rowData['transcode_decision'] === 'direct play') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Play"><i class="fa fa-play-circle fa-fw"></i></span>'; transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Play"><i class="fa fa-play-circle fa-fw"></i></span>';
} }
$(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + '&nbsp' + cellData + '</div></a></div>'); $(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + '&nbsp' + cellData + '</div></a></div>');
@ -148,7 +148,7 @@ history_table_modal_options = {
} }
} }
$('#history_table').on('click', 'td.modal-control', function () { $('.history_table').on('click', 'td.modal-control', function () {
var tr = $(this).parents('tr'); var tr = $(this).parents('tr');
var row = history_table.row(tr); var row = history_table.row(tr);
var rowData = row.data(); var rowData = row.data();

View file

@ -16,7 +16,7 @@ libraries_list_table_options = {
"pageLength": 10, "pageLength": 10,
"order": [ 2, 'asc'], "order": [ 2, 'asc'],
"autoWidth": true, "autoWidth": true,
"stateSave": false, "stateSave": true,
"pagingType": "bootstrap", "pagingType": "bootstrap",
"columnDefs": [ "columnDefs": [
{ {

View file

@ -5,7 +5,7 @@ var log_table_options = {
"pagingType": "bootstrap", "pagingType": "bootstrap",
"order": [ 0, 'desc'], "order": [ 0, 'desc'],
"pageLength": 50, "pageLength": 50,
"stateSave": false, "stateSave": true,
"language": { "language": {
"search":"Search: ", "search":"Search: ",
"lengthMenu":"Show _MENU_ lines per page", "lengthMenu":"Show _MENU_ lines per page",

View file

@ -23,7 +23,7 @@ media_info_table_options = {
"emptyTable": "No data in table" "emptyTable": "No data in table"
}, },
"pagingType": "bootstrap", "pagingType": "bootstrap",
"stateSave": false, "stateSave": true,
"processing": false, "processing": false,
"serverSide": true, "serverSide": true,
"pageLength": 25, "pageLength": 25,
@ -294,7 +294,7 @@ media_info_table_options = {
} }
// Parent table expand detailed media info // Parent table expand detailed media info
$('#media_info_table').on('click', '> tbody > tr > td.expand-media-info a', function () { $('.media_info_table').on('click', '> tbody > tr > td.expand-media-info a', function () {
var tr = $(this).closest('tr'); var tr = $(this).closest('tr');
var row = media_info_table.row(tr); var row = media_info_table.row(tr);
var rowData = row.data(); var rowData = row.data();

View file

@ -5,7 +5,7 @@ notification_log_table_options = {
"pagingType": "bootstrap", "pagingType": "bootstrap",
"order": [ 0, 'desc'], "order": [ 0, 'desc'],
"pageLength": 50, "pageLength": 50,
"stateSave": false, "stateSave": true,
"language": { "language": {
"search":"Search: ", "search":"Search: ",
"lengthMenu":"Show _MENU_ lines per page", "lengthMenu":"Show _MENU_ lines per page",

View file

@ -5,7 +5,7 @@ var plex_log_table_options = {
"pagingType": "bootstrap", "pagingType": "bootstrap",
"order": [ 0, 'desc'], "order": [ 0, 'desc'],
"pageLength": 50, "pageLength": 50,
"stateSave": false, "stateSave": true,
"language": { "language": {
"search":"Search: ", "search":"Search: ",
"lengthMenu":"Show _MENU_ lines per page", "lengthMenu":"Show _MENU_ lines per page",

View file

@ -4,7 +4,7 @@ sync_table_options = {
"pagingType": "bootstrap", "pagingType": "bootstrap",
"order": [ [ 0, 'desc'], [ 1, 'asc'], [2, 'asc'] ], "order": [ [ 0, 'desc'], [ 1, 'asc'], [2, 'asc'] ],
"pageLength": 25, "pageLength": 25,
"stateSave": false, "stateSave": true,
"language": { "language": {
"search":"Search: ", "search":"Search: ",
"lengthMenu":"Show _MENU_ lines per page", "lengthMenu":"Show _MENU_ lines per page",

View file

@ -8,7 +8,7 @@ user_ip_table_options = {
"infoFiltered":"(filtered from _MAX_ total entries)", "infoFiltered":"(filtered from _MAX_ total entries)",
"emptyTable": "No data in table", "emptyTable": "No data in table",
}, },
"stateSave": false, "stateSave": true,
"pagingType": "bootstrap", "pagingType": "bootstrap",
"processing": false, "processing": false,
"serverSide": true, "serverSide": true,
@ -56,7 +56,7 @@ user_ip_table_options = {
} }
}, },
"width": "15%", "width": "15%",
"className": "no-wrap hidden-md hidden-sm hidden-xs modal-control" "className": "no-wrap hidden-md hidden-sm hidden-xs"
}, },
{ {
"targets": [3], "targets": [3],
@ -64,11 +64,11 @@ user_ip_table_options = {
"createdCell": function (td, cellData, rowData, row, col) { "createdCell": function (td, cellData, rowData, row, col) {
if (cellData) { if (cellData) {
var transcode_dec = ''; var transcode_dec = '';
if (rowData['video_decision'] === 'transcode' || rowData['audio_decision'] === 'transcode') { if (rowData['transcode_decision'] === 'transcode') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Transcode"><i class="fa fa-server fa-fw"></i></span>'; transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Transcode"><i class="fa fa-server fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'copy' || rowData['audio_decision'] === 'copy') { } else if (rowData['transcode_decision'] === 'copy') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-video-camera fa-fw"></i></span>'; transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-video-camera fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'direct play' || rowData['audio_decision'] === 'direct play') { } else if (rowData['transcode_decision'] === 'direct play') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Play"><i class="fa fa-play-circle fa-fw"></i></span>'; transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Play"><i class="fa fa-play-circle fa-fw"></i></span>';
} }
$(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + '&nbsp' + cellData + '</div></a></div>'); $(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + '&nbsp' + cellData + '</div></a></div>');
@ -146,11 +146,11 @@ user_ip_table_options = {
} }
} }
$('#user_ip_table').on('mouseenter', 'td.modal-control span', function () { $('.user_ip_table').on('mouseenter', 'td.modal-control span', function () {
$(this).tooltip(); $(this).tooltip();
}); });
$('#user_ip_table').on('click', 'td.modal-control', function () { $('.user_ip_table').on('click', 'td.modal-control', function () {
var tr = $(this).parents('tr'); var tr = $(this).parents('tr');
var row = user_ip_table.row(tr); var row = user_ip_table.row(tr);
var rowData = row.data(); var rowData = row.data();
@ -169,7 +169,7 @@ $('#user_ip_table').on('click', 'td.modal-control', function () {
showStreamDetails(); showStreamDetails();
}); });
$('#user_ip_table').on('click', 'td.modal-control-ip', function () { $('.user_ip_table').on('click', 'td.modal-control-ip', function () {
var tr = $(this).parents('tr'); var tr = $(this).parents('tr');
var row = user_ip_table.row( tr ); var row = user_ip_table.row( tr );
var rowData = row.data(); var rowData = row.data();

View file

@ -16,7 +16,7 @@ users_list_table_options = {
"pageLength": 10, "pageLength": 10,
"order": [ 2, 'asc'], "order": [ 2, 'asc'],
"autoWidth": true, "autoWidth": true,
"stateSave": false, "stateSave": true,
"pagingType": "bootstrap", "pagingType": "bootstrap",
"columnDefs": [ "columnDefs": [
{ {
@ -120,11 +120,11 @@ users_list_table_options = {
"createdCell": function (td, cellData, rowData, row, col) { "createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== null && cellData !== '') { if (cellData !== null && cellData !== '') {
var transcode_dec = ''; var transcode_dec = '';
if (rowData['video_decision'] === 'transcode' || rowData['audio_decision'] === 'transcode') { if (rowData['transcode_decision'] === 'transcode') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Transcode"><i class="fa fa-server fa-fw"></i></span>'; transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Transcode"><i class="fa fa-server fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'copy' || rowData['audio_decision'] === 'copy') { } else if (rowData['transcode_decision'] === 'copy') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-video-camera fa-fw"></i></span>'; transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-video-camera fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'direct play' || rowData['audio_decision'] === 'direct play') { } else if (rowData['transcode_decision'] === 'direct play') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Play"><i class="fa fa-play-circle fa-fw"></i></span>'; transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Play"><i class="fa fa-play-circle fa-fw"></i></span>';
} }
$(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + '&nbsp;' + cellData + '</div></a></div>'); $(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + '&nbsp;' + cellData + '</div></a></div>');

View file

@ -118,8 +118,16 @@ DOCUMENTATION :: END
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div class="table-card-header"> <div class="table-card-header">
<ul class="nav nav-header nav-dashboard pull-right">
<li>
<a href="#" id="recently-watched-page-left" class="paginate-watched btn-gray disabled" data-id="+1"><i class="fa fa-lg fa-chevron-left"></i></a>
</li>
<li>
<a href="#" id="recently-watched-page-right" class="paginate-watched btn-gray disabled" data-id="-1"><i class="fa fa-lg fa-chevron-right"></i></a>
</li>
</ul>
<div class="header-bar"> <div class="header-bar">
<span><i class="fa fa-history"></i> Recently Watched</span> <span><i class="fa fa-history"></i> Recently Played</span>
</div> </div>
</div> </div>
<div class="table-card-back"> <div class="table-card-back">
@ -135,6 +143,14 @@ DOCUMENTATION :: END
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div class="table-card-header"> <div class="table-card-header">
<ul class="nav nav-header nav-dashboard pull-right">
<li>
<a href="#" id="recently-added-page-left" class="paginate-added btn-gray disabled" data-id="+1"><i class="fa fa-lg fa-chevron-left"></i></a>
</li>
<li>
<a href="#" id="recently-added-page-right" class="paginate-added btn-gray disabled" data-id="-1"><i class="fa fa-lg fa-chevron-right"></i></a>
</li>
</ul>
<div class="header-bar"> <div class="header-bar">
<span><i class="fa fa-history"></i> Recently Added</span> <span><i class="fa fa-history"></i> Recently Added</span>
</div> </div>
@ -170,7 +186,7 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
<div class="table-card-back"> <div class="table-card-back">
<table class="display" id="history_table" width="100%"> <table class="display history_table" id="history_table-SID-${data['section_id']}" width="100%">
<thead> <thead>
<tr> <tr>
<th align="left" id="delete">Delete</th> <th align="left" id="delete">Delete</th>
@ -229,7 +245,7 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
<div class="table-card-back"> <div class="table-card-back">
<table class="display" id="media_info_table" width="100%"> <table class="display media_info_table" id="media_info_table-SID-${data['section_id']}" width="100%">
<thead> <thead>
<tr> <tr>
<th align="left" id="added_at">Added At</th> <th align="left" id="added_at">Added At</th>
@ -364,12 +380,12 @@ DOCUMENTATION :: END
}; };
} }
} }
history_table = $('#history_table').DataTable(history_table_options); history_table = $('#history_table-SID-${data["section_id"]}').DataTable(history_table_options);
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] }); var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] });
$(colvis.button()).appendTo('#button-bar-history'); $(colvis.button()).appendTo('#button-bar-history');
clearSearchButton('history_table', history_table); clearSearchButton('history_table-SID-${data["section_id"]}', history_table);
} }
function loadMediaInfoTable() { function loadMediaInfoTable() {
@ -385,12 +401,12 @@ DOCUMENTATION :: END
}; };
} }
} }
media_info_table = $('#media_info_table').DataTable(media_info_table_options); media_info_table = $('#media_info_table-SID-${data["section_id"]}').DataTable(media_info_table_options);
var colvis = new $.fn.dataTable.ColVis(media_info_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' }); var colvis = new $.fn.dataTable.ColVis(media_info_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' });
$(colvis.button()).appendTo('#button-bar-media-info'); $(colvis.button()).appendTo('#button-bar-media-info');
clearSearchButton('media_info_table', media_info_table); clearSearchButton('media_info_table-SID-${data["section_id"]}', media_info_table);
} }
$( "#history-tab-btn" ).one( "click", function() { $( "#history-tab-btn" ).one( "click", function() {
@ -460,61 +476,116 @@ DOCUMENTATION :: END
}); });
function recentlyWatched() { function recentlyWatched() {
var widthVal = $("#library-recently-watched").width();
var tmp = (widthVal-25) / 175;
if (tmp > 0) {
var containerSize = parseInt(tmp);
} else {
var containerSize = 1;
}
// Populate recently watched // Populate recently watched
$.ajax({ $.ajax({
url: 'get_library_recently_watched', url: 'get_library_recently_watched',
async: true, async: true,
data: { data: {
section_id: section_id, section_id: section_id,
limit: containerSize limit: 50
}, },
complete: function(xhr, status) { complete: function(xhr, status) {
$("#library-recently-watched").html(xhr.responseText); $("#library-recently-watched").html(xhr.responseText);
highlightWatchedScrollerButton();
} }
}); });
} }
function recentlyAdded() { function recentlyAdded() {
var widthVal = $("#library-recently-added").width();
var tmp = (widthVal-25) / 175;
if (tmp > 0) {
var containerSize = parseInt(tmp);
} else {
var containerSize = 1;
}
// Populate recently added // Populate recently added
$.ajax({ $.ajax({
url: 'get_library_recently_added', url: 'get_library_recently_added',
async: true, async: true,
data: { data: {
section_id: section_id, section_id: section_id,
limit: containerSize limit: 50
}, },
complete: function(xhr, status) { complete: function(xhr, status) {
$("#library-recently-added").html(xhr.responseText); $("#library-recently-added").html(xhr.responseText);
highlightAddedScrollerButton();
} }
}); });
} }
recentlyWatched(); recentlyWatched();
recentlyAdded(); recentlyAdded();
function highlightWatchedScrollerButton() {
var scroller = $("#recently-watched-row-scroller");
var numElems = scroller.find("li").length;
scroller.width(numElems * 175);
if (scroller.width() > $("#library-recently-watched").width()) {
$("#recently-watched-page-right").removeClass("disabled");
} else {
$("#recently-watched-page-right").addClass("disabled");
}
}
function highlightAddedScrollerButton() {
var scroller = $("#recently-added-row-scroller");
var numElems = scroller.find("li").length;
scroller.width(numElems * 175);
if (scroller.width() > $("#library-recently-added").width()) {
$("#recently-added-page-right").removeClass("disabled");
} else {
$("#recently-added-page-right").addClass("disabled");
}
}
$(window).resize(function() { $(window).resize(function() {
recentlyWatched(); highlightWatchedScrollerButton();
recentlyAdded(); highlightAddedScrollerButton();
}); });
$('div.art-face').animate({ opacity: 0.2 }, { duration: 1000 }); $('div.art-face').animate({ opacity: 0.2 }, { duration: 1000 });
var leftTotalWatched = 0;
$(".paginate-watched").click(function (e) {
e.preventDefault();
var scroller = $("#recently-watched-row-scroller");
var containerWidth = $("#library-recently-watched").width();
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
leftTotalWatched = Math.max(Math.min(leftTotalWatched + scrollAmount, 0), leftMax);
scroller.animate({ left: leftTotalWatched }, 250);
if (leftTotalWatched == 0) {
$("#recently-watched-page-left").addClass("disabled").blur();
} else {
$("#recently-watched-page-left").removeClass("disabled");
}
if (leftTotalWatched == leftMax) {
$("#recently-watched-page-right").addClass("disabled").blur();
} else {
$("#recently-watched-page-right").removeClass("disabled");
}
});
var leftTotalAdded = 0;
$(".paginate-added").click(function (e) {
e.preventDefault();
var scroller = $("#recently-added-row-scroller");
var containerWidth = $("#library-recently-added").width();
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
leftTotalAdded = Math.max(Math.min(leftTotalAdded + scrollAmount, 0), leftMax);
scroller.animate({ left: leftTotalAdded }, 250);
if (leftTotalAdded == 0) {
$("#recently-added-page-left").addClass("disabled").blur();
} else {
$("#recently-added-page-left").removeClass("disabled");
}
if (leftTotalAdded == leftMax) {
$("#recently-added-page-right").addClass("disabled").blur();
} else {
$("#recently-added-page-right").removeClass("disabled");
}
});
}); });
</script> </script>
% endif % endif

View file

@ -32,63 +32,65 @@ DOCUMENTATION :: END
% if data: % if data:
<div class="dashboard-recent-media-row"> <div class="dashboard-recent-media-row">
<ul class="dashboard-recent-media list-unstyled"> <div id="recently-added-row-scroller" style="left: 0;">
% for item in data: <ul class="dashboard-recent-media list-unstyled">
<li> % for item in data:
% if item['media_type'] == 'episode' or item['media_type'] == 'movie': <li>
<a href="info?rating_key=${item['rating_key']}"> % if item['media_type'] == 'episode' or item['media_type'] == 'movie':
<div class="dashboard-recent-media-poster"> <a href="info?rating_key=${item['rating_key']}">
% if item['media_type'] == 'episode': <div class="dashboard-recent-media-poster">
% if item['parent_thumb']: % if item['media_type'] == 'episode':
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['parent_thumb']}&width=300&height=450&fallback=poster);"> % if item['parent_thumb']:
% else: <div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['parent_thumb']}&width=300&height=450&fallback=poster);">
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['grandparent_thumb']}&width=300&height=450&fallback=poster);"> % else:
% endif <div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['grandparent_thumb']}&width=300&height=450&fallback=poster);">
% elif item['media_type'] == 'movie': % endif
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=450&fallback=poster);"> % elif item['media_type'] == 'movie':
% endif <div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=450&fallback=poster);">
<div class="dashboard-recent-media-overlay"> % endif
<div class="dashboard-recent-media-overlay-text" id="added_at-${item['rating_key']}"> <div class="dashboard-recent-media-overlay">
<script> <div class="dashboard-recent-media-overlay-text" id="added_at-${item['rating_key']}">
<script>
$('#added_at-${item['rating_key']}').text('Added ' + moment(${item['added_at']}, "X").fromNow())
</script>
</div>
</div>
</div>
</div>
<div class="dashboard-recent-media-metacontainer">
% if item['media_type'] == 'episode':
<h3 title="${item['grandparent_title']}">${item['grandparent_title']}</h3>
<h3 title="${item['title']}">${item['title']}</h3>
<h3 class="text-muted">S${item['parent_media_index']} &middot; E${item['media_index']}</h3>
% elif item['media_type'] == 'movie':
<h3 title="${item['title']}">${item['title']}</h3>
<h3 class="text-muted">${item['year']}</h3>
% endif
</div>
</a>
% elif item['media_type'] == 'album':
<a href="info?rating_key=${item['rating_key']}">
<div class="dashboard-recent-media-cover">
<div class="dashboard-recent-media-cover-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=300&fallback=cover);">
<div class="dashboard-recent-media-overlay">
<div class="dashboard-recent-media-overlay-text" id="added_at-${item['rating_key']}">
<script>
$('#added_at-${item['rating_key']}').text('Added ' + moment(${item['added_at']}, "X").fromNow()) $('#added_at-${item['rating_key']}').text('Added ' + moment(${item['added_at']}, "X").fromNow())
</script> </script>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> <div class="dashboard-recent-media-metacontainer">
<div class="dashboard-recent-media-metacontainer"> <h3 title="${item['parent_title']}">${item['parent_title']}</h3>
% if item['media_type'] == 'episode': <h3 class="text-muted">${item['title']}</h3>
<h3 title="${item['grandparent_title']}">${item['grandparent_title']}</h3>
<h3 title="${item['title']}">${item['title']}</h3>
<h3 class="text-muted">S${item['parent_media_index']} &middot; E${item['media_index']}</h3>
% elif item['media_type'] == 'movie':
<h3 title="${item['title']}">${item['title']}</h3>
<h3 class="text-muted">${item['year']}</h3>
% endif
</div>
</a>
% elif item['media_type'] == 'album':
<a href="info?rating_key=${item['rating_key']}">
<div class="dashboard-recent-media-cover">
<div class="dashboard-recent-media-cover-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=300&fallback=cover);">
<div class="dashboard-recent-media-overlay">
<div class="dashboard-recent-media-overlay-text" id="added_at-${item['rating_key']}">
<script>
$('#added_at-${item['rating_key']}').text('Added ' + moment(${item['added_at']}, "X").fromNow())
</script>
</div>
</div>
</div> </div>
</div> </a>
<div class="dashboard-recent-media-metacontainer"> % endif
<h3 title="${item['parent_title']}">${item['parent_title']}</h3> </li>
<h3 class="text-muted">${item['title']}</h3> % endfor
</div> </ul>
</a> </div>
% endif
</li>
% endfor
</ul>
</div> </div>
% else: % else:
<div class="text-muted">No stats to show. <div class="text-muted">No stats to show.

View file

@ -22,6 +22,7 @@ from plexpy import helpers
</div> </div>
<div class="button-bar"> <div class="button-bar">
<button class="btn btn-dark" id="clear-logs"><i class="fa fa-trash-o"></i> Clear log</button> <button class="btn btn-dark" id="clear-logs"><i class="fa fa-trash-o"></i> Clear log</button>
<button class="btn btn-dark" id="clear-notify-logs" style="display: none;"><i class="fa fa-trash-o"></i> Clear log</button>
</div> </div>
</div> </div>
<div class='table-card-back'> <div class='table-card-back'>
@ -156,24 +157,28 @@ from plexpy import helpers
$("#plexpy-logs-btn").click(function () { $("#plexpy-logs-btn").click(function () {
$("#clear-logs").show(); $("#clear-logs").show();
$("#clear-notify-logs").hide();
LoadPlexPyLogs(); LoadPlexPyLogs();
clearSearchButton('log_table', log_table); clearSearchButton('log_table', log_table);
}); });
$("#plex-logs-btn").click(function () { $("#plex-logs-btn").click(function () {
$("#clear-logs").hide(); $("#clear-logs").hide();
$("#clear-notify-logs").hide();
LoadPlexLogs(); LoadPlexLogs();
clearSearchButton('plex_log_table', plex_log_table); clearSearchButton('plex_log_table', plex_log_table);
}); });
$("#plex-scanner-logs-btn").click(function () { $("#plex-scanner-logs-btn").click(function () {
$("#clear-logs").hide(); $("#clear-logs").hide();
$("#clear-notify-logs").hide();
LoadPlexScannerLogs(); LoadPlexScannerLogs();
clearSearchButton('plex_scanner_log_table', plex_scanner_log_table); clearSearchButton('plex_scanner_log_table', plex_scanner_log_table);
}); });
$("#notification-logs-btn").click(function () { $("#notification-logs-btn").click(function () {
$("#clear-logs").hide(); $("#clear-logs").hide();
$("#clear-notify-logs").show();
LoadNotificationLogs(); LoadNotificationLogs();
clearSearchButton('notification_log_table', notification_log_table); clearSearchButton('notification_log_table', notification_log_table);
}); });
@ -185,6 +190,19 @@ from plexpy import helpers
} }
}); });
$("#clear-notify-logs").click(function () {
var r = confirm("Are you sure you want to clear the PlexPy notification log?");
if (r == true) {
$.ajax({
url: 'clearNotifyLogs',
type: 'POST',
success: function (data) {
notification_log_table.draw();
}
});
}
});
var timer; var timer;
function setRefresh() function setRefresh()
{ {
@ -200,8 +218,12 @@ from plexpy import helpers
timer = setInterval(function() { timer = setInterval(function() {
if ($("#tabs-1").hasClass("active")) { if ($("#tabs-1").hasClass("active")) {
log_table.ajax.reload(); log_table.ajax.reload();
} else { } else if ($("#tabs-2").hasClass("active")) {
plex_log_table.ajax.reload(); plex_log_table.ajax.reload();
} else if ($("#tabs-3").hasClass("active")) {
plex_scanner_log_table.ajax.reload();
} else if ($("#tabs-4").hasClass("active")) {
notification_log_table.ajax.reload();
} }
}, 1000*refreshrate.value); }, 1000*refreshrate.value);
} }

View file

@ -92,6 +92,13 @@ from plexpy import helpers
</label> </label>
<p class="help-block">Trigger notification when the Plex Media Server can be reached externally after being down.</p> <p class="help-block">Trigger notification when the Plex Media Server can be reached externally after being down.</p>
</div> </div>
<div class="checkbox">
<label>
<input type="checkbox" data-size="small" data-id="${data['id']}" data-config-name="${data['config_prefix']}_on_pmsupdate" ${helpers.checked(data['on_pmsupdate'])} class="toggle-switches">
Notify on Plex update available
</label>
<p class="help-block">Trigger notification when an update for the Plex Media Server is available.</p>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -32,56 +32,58 @@ DOCUMENTATION :: END
% if data != None: % if data != None:
<div class="dashboard-recent-media-row"> <div class="dashboard-recent-media-row">
<ul class="dashboard-recent-media list-unstyled"> <div id="recently-added-row-scroller" style="left: 0;">
% for item in data: <ul class="dashboard-recent-media list-unstyled">
<div class="dashboard-recent-media-instance"> % for item in data:
<li> <div class="dashboard-recent-media-instance">
% if item['media_type'] == 'season' or item['media_type'] == 'movie': <li>
<a href="info?rating_key=${item['rating_key']}"> % if item['media_type'] == 'season' or item['media_type'] == 'movie':
<div class="dashboard-recent-media-poster"> <a href="info?rating_key=${item['rating_key']}">
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=450&fallback=poster);"> <div class="dashboard-recent-media-poster">
<div class="dashboard-recent-media-overlay"> <div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=450&fallback=poster);">
<div class="dashboard-recent-media-overlay-text" id="added_at-${item['rating_key']}"> <div class="dashboard-recent-media-overlay">
<script> <div class="dashboard-recent-media-overlay-text" id="added_at-${item['rating_key']}">
$('#added_at-${item['rating_key']}').text('Added ' + moment(${item['added_at']}, "X").fromNow()) <script>
</script> $('#added_at-${item['rating_key']}').text('Added ' + moment(${item['added_at']}, "X").fromNow())
</script>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> <div class="dashboard-recent-media-metacontainer">
<div class="dashboard-recent-media-metacontainer"> % if item['media_type'] == 'season':
% if item['media_type'] == 'season': <h3 title="${item['parent_title']}">${item['parent_title']}</h3>
<h3 title="${item['parent_title']}">${item['parent_title']}</h3> <h3 class="text-muted">${item['title']}</h3>
<h3 class="text-muted">${item['title']}</h3> % elif item['media_type'] == 'movie':
% elif item['media_type'] == 'movie': <h3 title="${item['title']}">${item['title']}</h3>
<h3 title="${item['title']}">${item['title']}</h3> <h3 class="text-muted">${item['year']}</h3>
<h3 class="text-muted">${item['year']}</h3> % endif
% endif </div>
</div> </a>
</a> % elif item['media_type'] == 'album':
% elif item['media_type'] == 'album': <a href="info?rating_key=${item['rating_key']}">
<a href="info?rating_key=${item['rating_key']}"> <div class="dashboard-recent-media-cover">
<div class="dashboard-recent-media-cover"> <div class="dashboard-recent-media-cover-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=300&fallback=cover);">
<div class="dashboard-recent-media-cover-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=300&fallback=cover);"> <div class="dashboard-recent-media-overlay">
<div class="dashboard-recent-media-overlay"> <div class="dashboard-recent-media-overlay-text" id="added_at-${item['rating_key']}">
<div class="dashboard-recent-media-overlay-text" id="added_at-${item['rating_key']}"> <script>
<script> $('#added_at-${item['rating_key']}').text('Added ' + moment(${item['added_at']}, "X").fromNow())
$('#added_at-${item['rating_key']}').text('Added ' + moment(${item['added_at']}, "X").fromNow()) </script>
</script> </div>
</div> </div>
</div> </div>
</div> </div>
</div> <div class="dashboard-recent-media-metacontainer">
<div class="dashboard-recent-media-metacontainer"> <h3 title="${item['parent_title']}">${item['parent_title']}</h3>
<h3 title="${item['parent_title']}">${item['parent_title']}</h3> <h3 class="text-muted">${item['title']}</h3>
<h3 class="text-muted">${item['title']}</h3> </div>
</div> </a>
</a> % endif
% endif </li>
</li> </div>
</div> % endfor
% endfor </ul>
</ul> </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. Please check your <a href="settings">settings</a>.

View file

@ -43,7 +43,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
<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-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">Plex.tv Account</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">Extra Settings</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">Monitoring</a></li> <li role="presentation"><a href="#tabs-8" aria-controls="tabs-8" role="tab" data-toggle="tab">Activity Monitoring</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-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> <li role="presentation"><a href="#tabs-10" aria-controls="tabs-10" role="tab" data-toggle="tab">Notification Agents</a></li>
</ul> </ul>
@ -82,15 +82,15 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</tr> </tr>
<tr> <tr>
<td>Backup Directory:</td> <td>Backup Directory:</td>
<td>${plexpy.CONFIG.BACKUP_DIR}</td> <td>${config['backup_dir']}</td>
</tr> </tr>
<tr> <tr>
<td>Cache Directory:</td> <td>Cache Directory:</td>
<td>${plexpy.CONFIG.CACHE_DIR}</td> <td>${config['cache_dir']}</td>
</tr> </tr>
<tr> <tr>
<td>Log Directory:</td> <td>Log Directory:</td>
<td>${plexpy.CONFIG.LOG_DIR}</td> <td>${config['log_dir']}</td>
</tr> </tr>
% if plexpy.ARGS: % if plexpy.ARGS:
<tr> <tr>
@ -169,6 +169,35 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</label> </label>
<p class="help-block">Group successive play history by the same user as a single entry in the tables and watch statistics.</p> <p class="help-block">Group successive play history by the same user as a single entry in the tables and watch statistics.</p>
</div> </div>
<div class="padded-header">
<h3>Directories</h3>
</div>
<div class="form-group">
<label for="backup_dir">Backup Directory</label>
<div class="row">
<div class="col-md-6">
<input type="text" class="form-control directory-settings" id="backup_dir" name="backup_dir" value="${config['backup_dir']}">
</div>
</div>
</div>
<div class="form-group">
<label for="cache_dir">Cache Directory</label>
<div class="row">
<div class="col-md-6">
<input type="text" class="form-control directory-settings" id="cache_dir" name="cache_dir" value="${config['cache_dir']}">
</div>
</div>
</div>
<div class="form-group">
<label for="log_dir">Log Directory</label>
<div class="row">
<div class="col-md-6">
<input type="text" class="form-control directory-settings" id="log_dir" name="log_dir" value="${config['log_dir']}">
</div>
</div>
</div>
<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-2"> <div role="tabpanel" class="tab-pane" id="tabs-2">
@ -437,9 +466,24 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
<div role="tabpanel" class="tab-pane" id="tabs-5"> <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 <small style="color: #fff;">Version <span id="pms_version">unknown</span></small></h3>
</div> </div>
<p class="help-block">If you're using websocket monitoring, any server changes require a restart of PlexPy.</p> <p class="help-block">If you're using websocket monitoring, any server changes require a restart of PlexPy.</p>
<div class="checkbox">
<label>
<input type="checkbox" id="monitor_pms_updates" name="monitor_pms_updates" value="1" ${config['monitor_pms_updates']}> Monitor Plex Updates
</label>
<p class="help-block">Enable to have PlexPy check if updates are available for the Plex Media Server.<br />
Note: The Plex updater is broken on certain Plex Pass version of Plex Media Server. PlexPy will automatically disable checking for Plex updates if one of these versions is found.</p>
</div>
<div class="checkbox">
<label>
<input type="checkbox" id="monitor_remote_access" name="monitor_remote_access" value="1" ${config['monitor_remote_access']}> Monitor Plex Remote Access
</label>
<span id="remoteAccessCheck" style="color: #eb8600; padding-left: 10px;"></span>
<p class="help-block">Enable to have PlexPy check if remote access to the Plex Media Server goes down.</p>
</div>
<div class="form-group has-feedback" id="pms-ip-group"> <div class="form-group has-feedback" id="pms-ip-group">
<label for="pms_ip">Plex IP or Hostname</label> <label for="pms_ip">Plex IP or Hostname</label>
<div class="row"> <div class="row">
@ -485,8 +529,9 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
<label for="pms_logs_folder">Logs Folder</label> <label for="pms_logs_folder">Logs Folder</label>
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<input type="text" class="form-control" id="pms_logs_folder" name="pms_logs_folder" value="${config['pms_logs_folder']}" size="30" data-parsley-trigger="change"> <input type="text" class="form-control" id="pms_logs_folder" name="pms_logs_folder" value="${config['pms_logs_folder']}" size="30" data-parsley-trigger="change" data-parsley-pattern="^[^\~\%]" data-parsley-errors-container="#pms_logs_folder_error" data-parsley-error-message="Shortcuts are not recognized.">
</div> </div>
<div id="pms_logs_folder_error" class="alert alert-danger settings-alert" role="alert"></div>
</div> </div>
<p class="help-block">Set the complete folder path where your Plex Server logs are, shortcuts are not recognized.<br /> <p class="help-block">Set the complete folder path where your Plex Server logs are, shortcuts are not recognized.<br />
<a href="${anon_url('https://support.plex.tv/hc/en-us/articles/200250417-Plex-Media-Server-Log-Files')}" target="_blank">Click here</a> for help. This is required if you enable IP logging (for PMS 0.9.12 and below). </p> <a href="${anon_url('https://support.plex.tv/hc/en-us/articles/200250417-Plex-Media-Server-Log-Files')}" target="_blank">Click here</a> for help. This is required if you enable IP logging (for PMS 0.9.12 and below). </p>
@ -613,13 +658,6 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</label> </label>
<p class="help-block">Instead of polling the server at regular intervals let the server tell PlexPy when something happens.</p> <p class="help-block">Instead of polling the server at regular intervals let the server tell PlexPy when something happens.</p>
</div> </div>
<div class="checkbox">
<label>
<input type="checkbox" id="monitor_remote_access" name="monitor_remote_access" value="1" ${config['monitor_remote_access']}> Monitor Plex Remote Access
</label>
<span id="remoteAccessCheck" style="color: #eb8600; padding-left: 10px;"></span>
<p class="help-block">Enable to have PlexPy check if remote access to the Plex Media Server goes down.</p>
</div>
<div class="padded-header"> <div class="padded-header">
<h3>History Logging</h3> <h3>History Logging</h3>
@ -749,8 +787,14 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
<label> <label>
<input type="checkbox" name="notify_recently_added_grandparent" id="notify_recently_added_grandparent" value="1" ${config['notify_recently_added_grandparent']}> Group notifications for recently added TV Shows or Music <input type="checkbox" name="notify_recently_added_grandparent" id="notify_recently_added_grandparent" value="1" ${config['notify_recently_added_grandparent']}> Group notifications for recently added TV Shows or Music
</label> </label>
<p class="help-block">Enable to only get one TV Show or Artist notification for a batch of recently added Episodes or Tracks. Movies are unaffected.<br /> <p class="help-block">
Note: No Season/Episode or Album/Track metadata will be available.</p> Enable to only get one TV Show or Artist notification for a batch of recently added Episodes or Tracks. Movies are unaffected.<br />
% if config['notify_recently_added_grandparent'] == 'Checked':
<span id="notify_recently_added_grandparent_note" style="color: #eb8600;">Note: No Season/Episode or Album/Track metadata will be available.</span>
% else:
<span id="notify_recently_added_grandparent_note">Note: No Season/Episode or Album/Track metadata will be available.</span>
% endif
</p>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="notify_recently_added_delay">Notification Delay</label> <label for="notify_recently_added_delay">Notification Delay</label>
@ -965,6 +1009,23 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</li> </li>
</ul> </ul>
</li> </li>
<li>
<div class="link"><i class="fa fa-refresh fa-fw"></i>&nbsp;Plex Update Available<i class="fa fa-chevron-down"></i></div>
<ul class="submenu">
<li>
<div class="form-group">
<label for="notify_on_pmsupdate_subject_text">Subject Line</label>
<input class="form-control" type="text" id="notify_on_pmsupdate_subject_text" name="notify_on_pmsupdate_subject_text" value="${config['notify_on_pmsupdate_subject_text']}" data-parsley-trigger="change" required>
<p class="help-block">Set a custom subject line.</p>
</div>
<div class="form-group">
<label for="notify_on_extup_body_text">Message Body</label>
<textarea class="form-control" id="notify_on_pmsupdate_body_text" name="notify_on_pmsupdate_body_text" data-parsley-trigger="change" data-autoresize required>${config['notify_on_pmsupdate_body_text']}</textarea>
<p class="help-block">Set a custom body.</p>
</div>
</li>
</ul>
</li>
</ul> </ul>
<ul id="accordion-scripts" class="accordion list-unstyled"> <ul id="accordion-scripts" class="accordion list-unstyled">
<li> <li>
@ -1025,24 +1086,45 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
<h4 class="modal-title">Date &amp; Time Format Options</h4> <h4 class="modal-title">Date &amp; Time Format Options</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<table> <table class="notification-params time-options">
<thead>
<tr>
<th>
Year
</th>
</tr>
</thead>
<tbody> <tbody>
<tr>
<td align="center" colspan="3"><h5>Year</h5></td>
</tr>
<tr> <tr>
<td><strong>YYYY</strong></td> <td><strong>YYYY</strong></td>
<td>Numeric, 4 digits</td> <td>Numeric, four digits</td>
<td>Eg., 1999, 2003</td> <td>Eg., 1999, 2003</td>
</tr> </tr>
<tr> <tr>
<td><strong>YY</strong></td> <td><strong>YY</strong></td>
<td>Numeric, 2 digits</td> <td>Numeric, two digits</td>
<td>Eg., 99, 03</td> <td>Eg., 99, 03</td>
</tr> </tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr> <tr>
<td align="center" colspan="3"><h5>Month</h5></td> <th>
Month
</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>MMMM</strong></td>
<td>Textual, full</td>
<td>January-December</td>
</tr>
<tr>
<td><strong>MMM</strong></td>
<td>Textual, three letters</td>
<td>Jan-Dec</td>
</tr> </tr>
<tr> <tr>
<td><strong>MM</strong></td> <td><strong>MM</strong></td>
@ -1054,23 +1136,41 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
<td>Numeric, without leading zeros</td> <td>Numeric, without leading zeros</td>
<td>1-12</td> <td>1-12</td>
</tr> </tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr> <tr>
<td><strong>MMMM</strong></td> <th>
<td>Textual full</td> Day of the Year
<td>January-December</td> </th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>DDDD</strong></td>
<td>Numeric, with leading zeros</td>
<td>001-365</td>
</tr> </tr>
<tr> <tr>
<td><strong>MMM</strong></td> <td><strong>DDD</strong></td>
<td>Textual three letters</td> <td>Numeric, without leading zeros</td>
<td>Jan-Dec</td> <td>1-365</td>
</tr> </tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr> <tr>
<td align="center" colspan="3"><h5>Day</h5></td> <th>
Day of the Month
</th>
</tr> </tr>
</thead>
<tbody>
<tr> <tr>
<td width="100"><strong>DD</strong></td> <td><strong>DD</strong></td>
<td width="300">Numeric, with leading zeros</td> <td>Numeric, with leading zeros</td>
<td>01-31</td> <td>01-31</td>
</tr> </tr>
<tr> <tr>
@ -1080,57 +1180,165 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</tr> </tr>
<tr> <tr>
<td><strong>Do</strong></td> <td><strong>Do</strong></td>
<td>The English suffix for the day of the month</td> <td>Numeric, with suffix</td>
<td>st, nd or th in the 1st, 2nd or 15th.</td> <td>Eg., 1st, 2nd ... 31st.</td>
</tr> </tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr> <tr>
<td align="center" colspan="3"><h5>Time</h5></td> <th>
Day of the Week
</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>dddd</strong></td>
<td>Textual, full</td>
<td>Sunday-Saturday</td>
</tr> </tr>
<tr> <tr>
<td><strong>a</strong></td> <td><strong>ddd</strong></td>
<td width="300">am/pm Lowercase</td> <td>Textual, three letters</td>
<td>am, pm</td> <td>Sun-Sat</td>
</tr> </tr>
<tr> <tr>
<td><strong>A</strong></td> <td><strong>d</strong></td>
<td>AM/PM Uppercase</td> <td>Numeric</td>
<td>AM, PM</td> <td>0-6</td>
</tr> </tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr> <tr>
<td><strong>h</strong></td> <th>
<td>Hour, 12-hour, without leading zeros</td> Hour
<td>1-12</td> </th>
</tr>
<tr>
<td><strong>hh</strong></td>
<td>Hour, 12-hour, with leading zeros</td>
<td>01-12</td>
</tr>
<tr>
<td><strong>H</strong></td>
<td>Hour, 24-hour, without leading zeros</td>
<td>0-23</td>
</tr> </tr>
</thead>
<tbody>
<tr> <tr>
<td><strong>HH</strong></td> <td><strong>HH</strong></td>
<td>Hour, 24-hour, with leading zeros</td> <td>24-hour, with leading zeros</td>
<td>00-23</td> <td>00-23</td>
</tr> </tr>
<tr>
<td><strong>H</strong></td>
<td>24-hour, without leading zeros</td>
<td>0-23</td>
</tr>
<tr>
<td><strong>hh</strong></td>
<td>12-hour, with leading zeros</td>
<td>01-12</td>
</tr>
<tr>
<td><strong>h</strong></td>
<td>12-hour, without leading zeros</td>
<td>1-12</td>
</tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr>
<th>
Minute
</th>
</tr>
</thead>
<tbody>
<tr> <tr>
<td><strong>mm</strong></td> <td><strong>mm</strong></td>
<td>Minutes, with leading zeros</td> <td>Numeric, with leading zeros</td>
<td>00-59</td> <td>00-59</td>
</tr> </tr>
<tr>
<td><strong>m</strong></td>
<td>Numeric, without leading zeros</td>
<td>0-59</td>
</tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr>
<th>
Second
</th>
</tr>
</thead>
<tbody>
<tr> <tr>
<td><strong>ss</strong></td> <td><strong>ss</strong></td>
<td>Seconds, with leading zeros</td> <td>Numeric, with leading zeros</td>
<td>00-59</td> <td>00-59</td>
</tr> </tr>
<tr> <tr>
<td><strong>zz</strong></td> <td><strong>s</strong></td>
<td>Timezone abbreviation</td> <td>Numeric, without leading zeros</td>
<td>Eg., EST, MDT ...</td> <td>0-59</td>
</tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr>
<th>
AM / PM
</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>A</strong></td>
<td>AM/PM uppercase</td>
<td>AM, PM</td>
</tr>
<tr>
<td><strong>a</strong></td>
<td width="300">am/pm lowercase</td>
<td>am, pm</td>
</tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr>
<th>
Timezone
</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>ZZ</strong></td>
<td>UTC offset</td>
<td>Eg., +0100, -0700</td>
</tr>
<tr>
<td><strong>Z</strong></td>
<td>UTC offset</td>
<td>Eg., +01:00, -07:00</td>
</tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr>
<th>
Timestamp
</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>X</strong></td>
<td>Unix timestamp</td>
<td>Eg., 1456887825</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -1215,6 +1423,10 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
<td><strong>{server_uptime}</strong></td> <td><strong>{server_uptime}</strong></td>
<td>The uptime (in days, hours, mins, secs) of your Plex Server.</td> <td>The uptime (in days, hours, mins, secs) of your Plex Server.</td>
</tr> </tr>
<tr>
<td><strong>{server_version}</strong></td>
<td>The current version of your Plex Server.</td>
</tr>
<tr> <tr>
<td><strong>{action}</strong></td> <td><strong>{action}</strong></td>
<td>The action that triggered the notification.</td> <td>The action that triggered the notification.</td>
@ -1407,7 +1619,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</tr> </tr>
<tr> <tr>
<td><strong>{artist_name}</strong></td> <td><strong>{artist_name}</strong></td>
<td>The name of the artistd.</td> <td>The name of the artist.</td>
</tr> </tr>
<tr> <tr>
<td><strong>{album_name}</strong></td> <td><strong>{album_name}</strong></td>
@ -1521,7 +1733,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</tr> </tr>
<tr> <tr>
<td><strong>{lastfm_url}</strong></td> <td><strong>{lastfm_url}</strong></td>
<td>The last.fm URL for the album. <td>The Last.fm URL for the album.
<p class="small-muted">(PMS agent must be Last.fm)</p></td> <p class="small-muted">(PMS agent must be Last.fm)</p></td>
</tr> </tr>
<tr> <tr>
@ -1546,6 +1758,29 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</tr> </tr>
</tbody> </tbody>
</table> </table>
<table class="notification-params">
<thead>
<tr>
<th>
Plex Update Available
</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>{update_version}</strong></td>
<td>The available update version for your Plex Server.</td>
</tr>
<tr>
<td><strong>{update_url}</strong></td>
<td>The available update download URL.</td>
</tr>
<tr>
<td><strong>{update_changelog}</strong></td>
<td>The changelog for the available update.</td>
</tr>
</tbody>
</table>
</div> </div>
</div> </div>
<div class="modal-footer"></div> <div class="modal-footer"></div>
@ -1650,10 +1885,11 @@ $(document).ready(function() {
authChanged = false; authChanged = false;
httpChanged = false; httpChanged = false;
monitorChanged = false; monitorChanged = false;
directoryChanged = false;
// Alert the user that their changes require a restart. // Alert the user that their changes require a restart.
function postSaveChecks() { function postSaveChecks() {
if ((serverChanged && $('#monitoring_use_websocket').is(":checked")) || authChanged || httpChanged || monitorChanged) { if ((serverChanged && $('#monitoring_use_websocket').is(":checked")) || authChanged || httpChanged || monitorChanged || directoryChanged) {
$('#restart-modal').modal('show'); $('#restart-modal').modal('show');
} }
} }
@ -1766,6 +2002,10 @@ $(document).ready(function() {
monitorChanged = true; monitorChanged = true;
}); });
$( ".directory-settings" ).change(function() {
directoryChanged = true;
});
$( ".pms-settings" ).change(function() { $( ".pms-settings" ).change(function() {
serverChanged = true; serverChanged = true;
$("#pms_identifier").val(""); $("#pms_identifier").val("");
@ -1899,50 +2139,62 @@ $(document).ready(function() {
$.get("/osxnotifyregister", { 'app': osx_notify_app }, function (data) { showMsg("<div class='msg'><span class='ui-icon ui-icon-check'></span>" + data + "</div>", false, true, 3000); }); $.get("/osxnotifyregister", { 'app': osx_notify_app }, function (data) { showMsg("<div class='msg'><span class='ui-icon ui-icon-check'></span>" + data + "</div>", false, true, 3000); });
}) })
pms_version = false;
pms_logs_debug = false;
pms_logs = false;
// Checks to see if PMS server version is >= 0.9.14 with automaatically logged IP addresses
$.ajax({ $.ajax({
url: 'get_server_identity', url: 'get_server_identity',
async: true, async: true,
success: function(data) { success: function(data) {
var version = data.version.split('.') if (data.version){ $("#pms_version").text(data.version); }
if (parseInt(version[0]) >= 0 && parseInt(version[1]) >= 9 && parseInt(version[2]) >= 14) { var version = (data.version ? data.version.split('.') : null);
if (version && parseInt(version[0]) >= 0 && parseInt(version[1]) >= 9 && parseInt(version[2]) >= 14) {
$("#debugLogCheck").html("IP address is automatically logged for PMS version 0.9.14 and above."); $("#debugLogCheck").html("IP address is automatically logged for PMS version 0.9.14 and above.");
$("#ip_logging_enable").attr("disabled", true); $("#ip_logging_enable").attr("disabled", true);
$("#ip_logging_enable").attr("checked", true); $("#ip_logging_enable").attr("checked", true);
pms_version = true;
checkLogsPath();
} else { } else {
// Check to see if debug logs are enabled on the PMS.
$.ajax({ $.ajax({
url: 'get_server_pref', url: 'get_server_pref',
data: { pref: 'logDebug' }, data: { pref: 'logDebug' },
async: true, async: true,
success: function(data) { success: function(data) {
if (data !== 'true') { pms_logs_debug = (data == 'true' ? true : false);
$("#debugLogCheck").html("Debug logging must be enabled on your Plex Server. <a target='_blank' href='${anon_url('https://support.plex.tv/hc/en-us/articles/201643703-Reporting-issues-with-Plex-Media-Server')}'>Click here</a> for help."); // Check to see if our logs folder is set before allowing IP logging to be enabled.
$("#ip_logging_enable").attr("disabled", true); checkLogsPath();
$("#ip_logging_enable").attr("checked", false);
}
} }
}); });
// Check to see if our logs folder is set before allowing IP logging to be enabled.
checkLogsPath();
$("#pms_logs_folder").change(function() {
checkLogsPath();
});
function checkLogsPath() {
if ($("#pms_logs_folder").val() == '') {
$("#debugLogCheck").html("You must first define your Plex Server Logs folder path under the Plex Media Server tab.");
$("#ip_logging_enable").attr("disabled", true);
$("#ip_logging_enable").attr("checked", false);
} else {
$("#ip_logging_enable").attr("disabled", false);
$("#debugLogCheck").html("");
}
}
} }
} }
}); });
$("#pms_logs_folder").change(function() {
checkLogsPath();
});
function checkLogsPath() {
pms_logs = ($("#pms_logs_folder").val() == '' ? false : true);
// Toggle IP logging checkbox depending on debug logs, and logs path
if (!(pms_version)) {
if (pms_logs_debug && pms_logs) {
$("#ip_logging_enable").attr("disabled", false);
$("#debugLogCheck").html("");
} else if (!(pms_logs_debug)) {
$("#debugLogCheck").html("Debug logging must be enabled on your Plex Server. <a target='_blank' href='${anon_url('https://support.plex.tv/hc/en-us/articles/201643703-Reporting-issues-with-Plex-Media-Server')}'>Click here</a> for help.");
$("#ip_logging_enable").attr("disabled", true);
$("#ip_logging_enable").attr("checked", false);
} else {
$("#debugLogCheck").html("You must first define your Plex Server Logs folder path under the Plex Media Server tab.");
$("#ip_logging_enable").attr("disabled", true);
$("#ip_logging_enable").attr("checked", false);
}
}
}
$.ajax({ $.ajax({
url: 'get_server_pref', url: 'get_server_pref',
data: { pref: 'PublishServerOnPlexOnlineKey' }, data: { pref: 'PublishServerOnPlexOnlineKey' },
@ -2055,6 +2307,10 @@ $(document).ready(function() {
} }
getSchedulerTable(); getSchedulerTable();
$("#notify_recently_added_grandparent").change(function () {
var c = this.checked ? '#eb8600' : '#737373';
$('#notify_recently_added_grandparent_note').css('color', c);
});
}); });
</script> </script>
</%def> </%def>

View file

@ -112,8 +112,16 @@ from plexpy import helpers
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div class="table-card-header"> <div class="table-card-header">
<ul class="nav nav-header nav-dashboard pull-right">
<li>
<a href="#" id="recently-watched-page-left" class="paginate btn-gray disabled" data-id="+1"><i class="fa fa-lg fa-chevron-left"></i></a>
</li>
<li>
<a href="#" id="recently-watched-page-right" class="paginate btn-gray" data-id="-1"><i class="fa fa-lg fa-chevron-right"></i></a>
</li>
</ul>
<div class="header-bar"> <div class="header-bar">
<span><i class="fa fa-history"></i> Recently Watched</span> <span><i class="fa fa-history"></i> Recently Played</span>
</div> </div>
</div> </div>
<div class="table-card-back"> <div class="table-card-back">
@ -140,7 +148,7 @@ from plexpy import helpers
</div> </div>
</div> </div>
<div class="table-card-back"> <div class="table-card-back">
<table id="user_ip_table" class="display" width="100%"> <table class="display user_ip_table" id="user_ip_table-UID-${data['user_id']}" width="100%">
<thead> <thead>
<tr> <tr>
<th align="left">Last Seen</th> <th align="left">Last Seen</th>
@ -178,7 +186,7 @@ from plexpy import helpers
</div> </div>
</div> </div>
<div class="table-card-back"> <div class="table-card-back">
<table class="display" id="history_table" width="100%"> <table class="display history_table" id="history_table-UID-${data['user_id']}" width="100%">
<thead> <thead>
<tr> <tr>
<th align="left" id="delete">Delete</th> <th align="left" id="delete">Delete</th>
@ -218,7 +226,7 @@ from plexpy import helpers
</div> </div>
</div> </div>
<div class="table-card-back"> <div class="table-card-back">
<table class="display" id="sync_table" width="100%"> <table class="display" id="sync_table-UID-${data['user_id']}" width="100%">
<thead> <thead>
<tr> <tr>
<th align="left" id="state">State</th> <th align="left" id="state">State</th>
@ -348,13 +356,13 @@ from plexpy import helpers
}; };
} }
} }
history_table = $('#history_table').DataTable(history_table_options); history_table = $('#history_table-UID-${data["user_id"]}').DataTable(history_table_options);
history_table.column(2).visible(false); history_table.column(2).visible(false);
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] }); var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] });
$(colvis.button()).appendTo('#button-bar-history'); $(colvis.button()).appendTo('#button-bar-history');
clearSearchButton('history_table', history_table); clearSearchButton('history_table-UID-${data["user_id"]}', history_table);
$('#history_table_filter').prepend('<div class="btn-group" data-toggle="buttons" id="media_type-selection" style="padding-right: 15px;"> \ $('#history_table_filter').prepend('<div class="btn-group" data-toggle="buttons" id="media_type-selection" style="padding-right: 15px;"> \
<label class="btn btn-dark active"> \ <label class="btn btn-dark active"> \
@ -397,9 +405,9 @@ from plexpy import helpers
}; };
} }
} }
user_ip_table = $('#user_ip_table').DataTable(user_ip_table_options); user_ip_table = $('#user_ip_table-UID-${data["user_id"]}').DataTable(user_ip_table_options);
clearSearchButton('user_ip_table', user_ip_table); clearSearchButton('user_ip_table-UID-${data["user_id"]}', user_ip_table);
}); });
$( "#sync-tab-btn" ).one( "click", function() { $( "#sync-tab-btn" ).one( "click", function() {
@ -410,13 +418,13 @@ from plexpy import helpers
d.user_id = user_id; d.user_id = user_id;
} }
} }
sync_table = $('#sync_table').DataTable(sync_table_options); sync_table = $('#sync_table-UID-${data["user_id"]}').DataTable(sync_table_options);
sync_table.column(1).visible(false); sync_table.column(1).visible(false);
var colvis_sync = new $.fn.dataTable.ColVis( sync_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' } ); var colvis_sync = new $.fn.dataTable.ColVis( sync_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' } );
$( colvis_sync.button() ).appendTo('#button-bar-sync'); $( colvis_sync.button() ).appendTo('#button-bar-sync');
clearSearchButton('sync_table', sync_table); clearSearchButton('sync_table-UID-${data["user_id"]}', sync_table);
}); });
// Load edit user modal // Load edit user modal
@ -471,32 +479,60 @@ from plexpy import helpers
}); });
function recentlyWatched() { function recentlyWatched() {
var widthVal = $("#user-recently-watched").width();
var tmp = (widthVal-25) / 175;
if (tmp > 0) {
var containerSize = parseInt(tmp);
} else {
var containerSize = 1;
}
// Populate recently watched // Populate recently watched
$.ajax({ $.ajax({
url: 'get_user_recently_watched', url: 'get_user_recently_watched',
async: true, async: true,
data: { data: {
user_id: user_id, user_id: user_id,
limit: containerSize limit: 50
}, },
complete: function(xhr, status) { complete: function(xhr, status) {
$("#user-recently-watched").html(xhr.responseText); $("#user-recently-watched").html(xhr.responseText);
highlightWatchedScrollerButton();
} }
}); });
} }
recentlyWatched(); recentlyWatched();
function highlightWatchedScrollerButton() {
var scroller = $("#recently-watched-row-scroller");
var numElems = scroller.find("li").length;
scroller.width(numElems * 175);
if (scroller.width() > $("#user-recently-watched").width()) {
$("#recently-watched-page-right").removeClass("disabled");
} else {
$("#recently-watched-page-right").addClass("disabled");
}
}
$(window).resize(function() { $(window).resize(function() {
recentlyWatched(); highlightWatchedScrollerButton();
});
var leftTotal = 0;
$(".paginate").click(function (e) {
e.preventDefault();
var scroller = $("#recently-watched-row-scroller");
var containerWidth = $("#user-recently-watched").width();
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
leftTotal = Math.max(Math.min(leftTotal + scrollAmount, 0), leftMax);
scroller.animate({ left: leftTotal }, 250);
if (leftTotal == 0) {
$("#recently-watched-page-left").addClass("disabled").blur();
} else {
$("#recently-watched-page-left").removeClass("disabled");
}
if (leftTotal == leftMax) {
$("#recently-watched-page-right").addClass("disabled").blur();
} else {
$("#recently-watched-page-right").removeClass("disabled");
}
}); });
}); });
</script> </script>

View file

@ -28,56 +28,58 @@ DOCUMENTATION :: END
% if data: % if data:
<div class="dashboard-recent-media-row"> <div class="dashboard-recent-media-row">
<ul class="dashboard-recent-media list-unstyled"> <div id="recently-watched-row-scroller" style="left: 0;">
% for item in data: <ul class="dashboard-recent-media list-unstyled">
<li> % for item in data:
% if item['media_type'] == 'episode' or item['media_type'] == 'movie': <li>
<a href="info?source=history&rating_key=${item['rating_key']}"> % if item['media_type'] == 'episode' or item['media_type'] == 'movie':
<div class="dashboard-recent-media-poster"> <a href="info?source=history&rating_key=${item['rating_key']}">
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=450&fallback=poster);"> <div class="dashboard-recent-media-poster">
<div class="dashboard-recent-media-overlay"> <div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=450&fallback=poster);">
<div class="dashboard-recent-media-overlay-text" id="time-${item['time']}"> <div class="dashboard-recent-media-overlay">
<script> <div class="dashboard-recent-media-overlay-text" id="time-${item['time']}">
$('#time-${item['time']}').text('Watched ' + moment(${item['time']}, "X").fromNow()) <script>
</script> $('#time-${item['time']}').text('Watched ' + moment(${item['time']}, "X").fromNow())
</script>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> <div class="dashboard-recent-media-metacontainer">
<div class="dashboard-recent-media-metacontainer"> % if item['media_type'] == 'episode':
% if item['media_type'] == 'episode': <h3 title="${item['grandparent_title']}">${item['grandparent_title']}</h3>
<h3 title="${item['grandparent_title']}">${item['grandparent_title']}</h3> <h3 title="${item['title']}">${item['title']}</h3>
<h3 title="${item['title']}">${item['title']}</h3> <h3 class="text-muted">S${item['parent_media_index']} &middot; E${item['media_index']}</h3>
<h3 class="text-muted">S${item['parent_media_index']} &middot; E${item['media_index']}</h3> % elif item['media_type'] == 'movie':
% elif item['media_type'] == 'movie': <h3 title="${item['title']}">${item['title']}</h3>
<h3 title="${item['title']}">${item['title']}</h3> <h3 class="text-muted">${item['year']}</h3>
<h3 class="text-muted">${item['year']}</h3> % endif
% endif </div>
</div> </a>
</a> % elif item['media_type'] == 'track':
% elif item['media_type'] == 'track': <a href="info?source=history&rating_key=${item['rating_key']}">
<a href="info?source=history&rating_key=${item['rating_key']}"> <div class="dashboard-recent-media-cover">
<div class="dashboard-recent-media-cover"> <div class="dashboard-recent-media-cover-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=300&fallback=cover);">
<div class="dashboard-recent-media-cover-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=300&fallback=cover);"> <div class="dashboard-recent-media-overlay">
<div class="dashboard-recent-media-overlay"> <div class="dashboard-recent-media-overlay-text" id="time-${item['time']}">
<div class="dashboard-recent-media-overlay-text" id="time-${item['time']}"> <script>
<script> $('#time-${item['time']}').text('Watched ' + moment(${item['time']}, "X").fromNow())
$('#time-${item['time']}').text('Watched ' + moment(${item['time']}, "X").fromNow()) </script>
</script> </div>
</div> </div>
</div> </div>
</div> </div>
</div> <div class="dashboard-recent-media-metacontainer">
<div class="dashboard-recent-media-metacontainer"> <h3 title="${item['grandparent_title']}">${item['grandparent_title']}</h3>
<h3 title="${item['grandparent_title']}">${item['grandparent_title']}</h3> <h3 title="${item['title']}">${item['title']}</h3>
<h3 title="${item['title']}">${item['title']}</h3> <h3 class="text-muted">${item['parent_title']}</h3>
<h3 class="text-muted">${item['parent_title']}</h3> </div>
</div> </a>
</a> % endif
% endif </li>
</li> % endfor
% endfor </ul>
</ul> </div>
</div> </div>
% else: % else:
<div class="text-muted">No stats to show.</div><br> <div class="text-muted">No stats to show.</div><br>

View file

@ -33,12 +33,13 @@ load_rc_config ${name}
: ${plexpy_dir:="/usr/local/plexpy"} : ${plexpy_dir:="/usr/local/plexpy"}
: ${plexpy_chdir:="${plexpy_dir}"} : ${plexpy_chdir:="${plexpy_dir}"}
: ${plexpy_pid:="${plexpy_dir}/plexpy.pid"} : ${plexpy_pid:="${plexpy_dir}/plexpy.pid"}
: ${plexpy_flags:=""}
status_cmd="${name}_status" status_cmd="${name}_status"
stop_cmd="${name}_stop" stop_cmd="${name}_stop"
command="/usr/sbin/daemon" command="${plexpy_dir}/PlexPy.py"
command_args="python2 ${plexpy_dir}/PlexPy.py --daemon --pidfile ${plexpy_pid} --quiet --nolaunch" command_args="--daemon --pidfile ${plexpy_pid} --quiet --nolaunch ${plexpy_flags}"
# Ensure user is root when running this script. # Ensure user is root when running this script.
if [ `id -u` != "0" ]; then if [ `id -u` != "0" ]; then

5
init-scripts/init.freenas Normal file → Executable file
View file

@ -33,12 +33,13 @@ load_rc_config ${name}
: ${plexpy_dir:="/usr/local/share/plexpy"} : ${plexpy_dir:="/usr/local/share/plexpy"}
: ${plexpy_chdir:="${plexpy_dir}"} : ${plexpy_chdir:="${plexpy_dir}"}
: ${plexpy_pid:="${plexpy_dir}/plexpy.pid"} : ${plexpy_pid:="${plexpy_dir}/plexpy.pid"}
: ${plexpy_flags:=""}
status_cmd="${name}_status" status_cmd="${name}_status"
stop_cmd="${name}_stop" stop_cmd="${name}_stop"
command="/usr/sbin/daemon" command="${plexpy_dir}/PlexPy.py"
command_args="python2 ${plexpy_dir}/PlexPy.py --daemon --pidfile ${plexpy_pid} --quiet --nolaunch" command_args="--daemon --pidfile ${plexpy_pid} --quiet --nolaunch ${plexpy_flags}"
# Ensure user is root when running this script. # Ensure user is root when running this script.
if [ `id -u` != "0" ]; then if [ `id -u` != "0" ]; then

View file

@ -103,7 +103,7 @@ def initialize(config_file):
if not CONFIG.HTTPS_KEY: if not CONFIG.HTTPS_KEY:
CONFIG.HTTPS_KEY = os.path.join(DATA_DIR, 'server.key') CONFIG.HTTPS_KEY = os.path.join(DATA_DIR, 'server.key')
if not CONFIG.LOG_DIR.startswith(os.path.abspath(DATA_DIR)): if not CONFIG.LOG_DIR:
CONFIG.LOG_DIR = os.path.join(DATA_DIR, 'logs') CONFIG.LOG_DIR = os.path.join(DATA_DIR, 'logs')
if not os.path.exists(CONFIG.LOG_DIR): if not os.path.exists(CONFIG.LOG_DIR):
@ -120,8 +120,7 @@ def initialize(config_file):
logger.initLogger(console=not QUIET, log_dir=CONFIG.LOG_DIR, logger.initLogger(console=not QUIET, log_dir=CONFIG.LOG_DIR,
verbose=VERBOSE) verbose=VERBOSE)
if not CONFIG.BACKUP_DIR.startswith(os.path.abspath(DATA_DIR)): if not CONFIG.BACKUP_DIR:
# Put the backup dir in the data dir for now
CONFIG.BACKUP_DIR = os.path.join(DATA_DIR, 'backups') CONFIG.BACKUP_DIR = os.path.join(DATA_DIR, 'backups')
if not os.path.exists(CONFIG.BACKUP_DIR): if not os.path.exists(CONFIG.BACKUP_DIR):
try: try:
@ -129,14 +128,13 @@ def initialize(config_file):
except OSError as e: except OSError as e:
logger.error("Could not create backup dir '%s': %s", BACKUP_DIR, e) logger.error("Could not create backup dir '%s': %s", BACKUP_DIR, e)
if not CONFIG.CACHE_DIR.startswith(os.path.abspath(DATA_DIR)): if not CONFIG.CACHE_DIR:
# Put the cache dir in the data dir for now
CONFIG.CACHE_DIR = os.path.join(DATA_DIR, 'cache') CONFIG.CACHE_DIR = os.path.join(DATA_DIR, 'cache')
if not os.path.exists(CONFIG.CACHE_DIR): if not os.path.exists(CONFIG.CACHE_DIR):
try: try:
os.makedirs(CONFIG.CACHE_DIR) os.makedirs(CONFIG.CACHE_DIR)
except OSError as e: except OSError as e:
logger.error("Could not create cache dir '%s': %s", DATA_DIR, e) logger.error("Could not create cache dir '%s': %s", CACHE_DIR, e)
# Initialize the database # Initialize the database
logger.info('Checking to see if the database has all tables....') logger.info('Checking to see if the database has all tables....')
@ -306,6 +304,13 @@ def initialize_scheduler():
schedule_job(activity_pinger.check_recently_added, 'Check for recently added items', schedule_job(activity_pinger.check_recently_added, 'Check for recently added items',
hours=0, minutes=0, seconds=0) hours=0, minutes=0, seconds=0)
if CONFIG.MONITOR_PMS_UPDATES:
schedule_job(activity_pinger.check_server_updates, 'Check for Plex updates',
hours=12, minutes=0, seconds=0)
else:
schedule_job(activity_pinger.check_server_updates, 'Check for Plex updates',
hours=0, minutes=0, seconds=0)
if CONFIG.MONITOR_REMOTE_ACCESS: if CONFIG.MONITOR_REMOTE_ACCESS:
schedule_job(activity_pinger.check_server_response, 'Check for Plex remote access', schedule_job(activity_pinger.check_server_response, 'Check for Plex remote access',
hours=0, minutes=0, seconds=seconds) hours=0, minutes=0, seconds=seconds)
@ -390,7 +395,7 @@ def dbcheck():
# sessions table :: This is a temp table that logs currently active sessions # sessions table :: This is a temp table that logs currently active sessions
c_db.execute( c_db.execute(
'CREATE TABLE IF NOT EXISTS sessions (id INTEGER PRIMARY KEY AUTOINCREMENT, ' 'CREATE TABLE IF NOT EXISTS sessions (id INTEGER PRIMARY KEY AUTOINCREMENT, '
'session_key INTEGER, rating_key INTEGER, section_id INTEGER, media_type TEXT, started INTEGER, ' 'session_key INTEGER, rating_key INTEGER, section_id INTEGER, media_type TEXT, started INTEGER, stopped INTEGER, '
'paused_counter INTEGER DEFAULT 0, state TEXT, user_id INTEGER, user TEXT, friendly_name TEXT, ' 'paused_counter INTEGER DEFAULT 0, state TEXT, user_id INTEGER, user TEXT, friendly_name TEXT, '
'ip_address TEXT, machine_id TEXT, player TEXT, platform TEXT, title TEXT, parent_title TEXT, ' 'ip_address TEXT, machine_id TEXT, player TEXT, platform TEXT, title TEXT, parent_title TEXT, '
'grandparent_title TEXT, parent_rating_key INTEGER, grandparent_rating_key INTEGER, ' 'grandparent_title TEXT, parent_rating_key INTEGER, grandparent_rating_key INTEGER, '
@ -413,8 +418,8 @@ def dbcheck():
# session_history_media_info table :: This is a table which logs each session's media info # session_history_media_info table :: This is a table which logs each session's media info
c_db.execute( c_db.execute(
'CREATE TABLE IF NOT EXISTS session_history_media_info (id INTEGER PRIMARY KEY, ' 'CREATE TABLE IF NOT EXISTS session_history_media_info (id INTEGER PRIMARY KEY, rating_key INTEGER, '
'rating_key INTEGER, video_decision TEXT, audio_decision TEXT, duration INTEGER DEFAULT 0, width INTEGER, ' 'video_decision TEXT, audio_decision TEXT, transcode_decision TEXT, duration INTEGER DEFAULT 0, width INTEGER, '
'height INTEGER, container TEXT, video_codec TEXT, audio_codec TEXT, bitrate INTEGER, video_resolution TEXT, ' 'height INTEGER, container TEXT, video_codec TEXT, audio_codec TEXT, bitrate INTEGER, video_resolution TEXT, '
'video_framerate TEXT, aspect_ratio TEXT, audio_channels INTEGER, transcode_protocol TEXT, ' 'video_framerate TEXT, aspect_ratio TEXT, audio_channels INTEGER, transcode_protocol TEXT, '
'transcode_container TEXT, transcode_video_codec TEXT, transcode_audio_codec TEXT, ' 'transcode_container TEXT, transcode_video_codec TEXT, transcode_audio_codec TEXT, '
@ -614,6 +619,15 @@ def dbcheck():
'ALTER TABLE sessions ADD COLUMN section_id INTEGER' 'ALTER TABLE sessions ADD COLUMN section_id INTEGER'
) )
# Upgrade sessions table from earlier versions
try:
c_db.execute('SELECT stopped FROM sessions')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table sessions.")
c_db.execute(
'ALTER TABLE sessions ADD COLUMN stopped INTEGER'
)
# Upgrade session_history table from earlier versions # Upgrade session_history table from earlier versions
try: try:
c_db.execute('SELECT reference_id FROM session_history') c_db.execute('SELECT reference_id FROM session_history')
@ -664,6 +678,21 @@ def dbcheck():
'ALTER TABLE session_history_metadata ADD COLUMN section_id INTEGER' 'ALTER TABLE session_history_metadata ADD COLUMN section_id INTEGER'
) )
# Upgrade session_history_media_info table from earlier versions
try:
c_db.execute('SELECT transcode_decision FROM session_history_media_info')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table session_history_media_info.")
c_db.execute(
'ALTER TABLE session_history_media_info ADD COLUMN transcode_decision TEXT'
)
c_db.execute(
'UPDATE session_history_media_info SET transcode_decision = (CASE '
'WHEN video_decision = "transcode" OR audio_decision = "transcode" THEN "transcode" '
'WHEN video_decision = "copy" OR audio_decision = "copy" THEN "copy" '
'WHEN video_decision = "direct play" OR audio_decision = "direct play" THEN "direct play" END)'
)
# Upgrade users table from earlier versions # Upgrade users table from earlier versions
try: try:
c_db.execute('SELECT do_notify FROM users') c_db.execute('SELECT do_notify FROM users')
@ -890,7 +919,13 @@ def shutdown(restart=False, update=False):
if '--nolaunch' not in args: if '--nolaunch' not in args:
args += ['--nolaunch'] args += ['--nolaunch']
logger.info('Restarting PlexPy with %s', args) logger.info('Restarting PlexPy with %s', args)
os.execv(exe, args)
# os.execv fails with spaced names on Windows
# https://bugs.python.org/issue19066
if os.name == 'nt':
subprocess.Popen(args, cwd=os.getcwd())
else:
os.execv(exe, args)
os._exit(0) os._exit(0)

View file

@ -16,7 +16,7 @@
import time import time
import plexpy import plexpy
from plexpy import logger, pmsconnect, activity_processor, threading, notification_handler, helpers from plexpy import logger, pmsconnect, activity_processor, threading, notification_handler, helpers, notifiers
class ActivityHandler(object): class ActivityHandler(object):
@ -57,9 +57,11 @@ class ActivityHandler(object):
if self.is_valid_session() and self.get_live_session(): if self.is_valid_session() and self.get_live_session():
logger.debug(u"PlexPy ActivityHandler :: Session %s has started." % str(self.get_session_key())) logger.debug(u"PlexPy ActivityHandler :: Session %s has started." % str(self.get_session_key()))
# Fire off notifications # Check if any notification agents have notifications enabled
threading.Thread(target=notification_handler.notify, if any(d['on_play'] for d in notifiers.available_notification_agents()):
kwargs=dict(stream_data=self.get_live_session(), notify_action='play')).start() # Fire off notifications
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=self.get_live_session(), notify_action='play')).start()
# Write the new session to our temp session table # Write the new session to our temp session table
self.update_db_session() self.update_db_session()
@ -77,20 +79,24 @@ class ActivityHandler(object):
if not force_stop: if not force_stop:
ap.set_session_state(session_key=self.get_session_key(), ap.set_session_state(session_key=self.get_session_key(),
state=self.timeline['state'], state=self.timeline['state'],
view_offset=self.timeline['viewOffset']) view_offset=self.timeline['viewOffset'],
stopped=int(time.time()))
# Retrieve the session data from our temp table # Retrieve the session data from our temp table
db_session = ap.get_session_by_key(session_key=self.get_session_key()) db_session = ap.get_session_by_key(session_key=self.get_session_key())
# Fire off notifications # Check if any notification agents have notifications enabled
threading.Thread(target=notification_handler.notify, if any(d['on_stop'] for d in notifiers.available_notification_agents()):
kwargs=dict(stream_data=db_session, notify_action='stop')).start() # Fire off notifications
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=db_session, notify_action='stop')).start()
# Write it to the history table # Write it to the history table
monitor_proc = activity_processor.ActivityProcessor() monitor_proc = activity_processor.ActivityProcessor()
monitor_proc.write_session_history(session=db_session) monitor_proc.write_session_history(session=db_session)
# Remove the session from our temp session table # Remove the session from our temp session table
logger.debug(u"PlexPy ActivityHandler :: Removing session %s from session queue" % str(self.get_session_key()))
ap.delete_session(session_key=self.get_session_key()) ap.delete_session(session_key=self.get_session_key())
def on_pause(self): def on_pause(self):
@ -109,9 +115,11 @@ class ActivityHandler(object):
# Retrieve the session data from our temp table # Retrieve the session data from our temp table
db_session = ap.get_session_by_key(session_key=self.get_session_key()) db_session = ap.get_session_by_key(session_key=self.get_session_key())
# Fire off notifications # Check if any notification agents have notifications enabled
threading.Thread(target=notification_handler.notify, if any(d['on_pause'] for d in notifiers.available_notification_agents()):
kwargs=dict(stream_data=db_session, notify_action='pause')).start() # Fire off notifications
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=db_session, notify_action='pause')).start()
def on_resume(self): def on_resume(self):
if self.is_valid_session(): if self.is_valid_session():
@ -129,9 +137,11 @@ class ActivityHandler(object):
# Retrieve the session data from our temp table # Retrieve the session data from our temp table
db_session = ap.get_session_by_key(session_key=self.get_session_key()) db_session = ap.get_session_by_key(session_key=self.get_session_key())
# Fire off notifications # Check if any notification agents have notifications enabled
threading.Thread(target=notification_handler.notify, if any(d['on_resume'] for d in notifiers.available_notification_agents()):
kwargs=dict(stream_data=db_session, notify_action='resume')).start() # Fire off notifications
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=db_session, notify_action='resume')).start()
def on_buffer(self): def on_buffer(self):
if self.is_valid_session(): if self.is_valid_session():
@ -159,8 +169,11 @@ class ActivityHandler(object):
if plexpy.CONFIG.BUFFER_THRESHOLD > 0 and (current_buffer_count >= plexpy.CONFIG.BUFFER_THRESHOLD and \ if plexpy.CONFIG.BUFFER_THRESHOLD > 0 and (current_buffer_count >= plexpy.CONFIG.BUFFER_THRESHOLD and \
time_since_last_trigger == 0 or time_since_last_trigger >= plexpy.CONFIG.BUFFER_WAIT): time_since_last_trigger == 0 or time_since_last_trigger >= plexpy.CONFIG.BUFFER_WAIT):
ap.set_session_buffer_trigger_time(session_key=self.get_session_key()) ap.set_session_buffer_trigger_time(session_key=self.get_session_key())
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=db_stream, notify_action='buffer')).start() # Check if any notification agents have notifications enabled
if any(d['on_buffer'] for d in notifiers.available_notification_agents()):
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=db_stream, notify_action='buffer')).start()
# This function receives events from our websocket connection # This function receives events from our websocket connection
def process(self): def process(self):
@ -202,10 +215,17 @@ class ActivityHandler(object):
# Monitor if the stream has reached the watch percentage for notifications # Monitor if the stream has reached the watch percentage for notifications
# The only purpose of this is for notifications # The only purpose of this is for notifications
progress_percent = helpers.get_percent(self.timeline['viewOffset'], db_session['duration']) # Check if any notification agents have notifications enabled
if progress_percent >= plexpy.CONFIG.NOTIFY_WATCHED_PERCENT and this_state != 'buffering': notify_agents = [d['id'] for d in notifiers.available_notification_agents() if d['on_watched']]
threading.Thread(target=notification_handler.notify, # Get the current states for notifications from our db
kwargs=dict(stream_data=db_session, notify_action='watched')).start() notified_agents = [d['agent_id'] for d in notification_handler.get_notify_state(session=db_session)
if d['notify_action'] == 'watched'] if notify_agents else []
if any(a not in notified_agents for a in notify_agents):
progress_percent = helpers.get_percent(self.timeline['viewOffset'], db_session['duration'])
if progress_percent >= plexpy.CONFIG.NOTIFY_WATCHED_PERCENT and this_state != 'buffering':
# Rather not put this on it's own thread so we know it completes before our next event.
notification_handler.notify(stream_data=db_session, notify_action='watched')
else: else:
# We don't have this session in our table yet, start a new one. # We don't have this session in our table yet, start a new one.

View file

@ -13,7 +13,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>. # along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
from plexpy import logger, pmsconnect, plextv, notification_handler, database, helpers, activity_processor, libraries from plexpy import logger, pmsconnect, plextv, notification_handler, database, helpers, activity_processor, libraries, notifiers
import threading import threading
import plexpy import plexpy
@ -38,9 +38,12 @@ def check_active_sessions(ws_request=False):
if session_list: if session_list:
if int_ping_count >= 3: if int_ping_count >= 3:
logger.info(u"PlexPy Monitor :: The Plex Media Server is back up.") logger.info(u"PlexPy Monitor :: The Plex Media Server is back up.")
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline, # Check if any notification agents have notifications enabled
kwargs=dict(notify_action='intup')).start() if any(d['on_intup'] for d in notifiers.available_notification_agents()):
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(notify_action='intup')).start()
int_ping_count = 0 int_ping_count = 0
media_container = session_list['sessions'] media_container = session_list['sessions']
@ -58,16 +61,24 @@ def check_active_sessions(ws_request=False):
# Here we can check the play states # Here we can check the play states
if session['state'] != stream['state']: if session['state'] != stream['state']:
if session['state'] == 'paused': if session['state'] == 'paused':
# Push any notifications - logger.debug(u"PlexPy Monitor :: Session %s has been paused." % stream['session_key'])
# Push it on it's own thread so we don't hold up our db actions
threading.Thread(target=notification_handler.notify, # Check if any notification agents have notifications enabled
kwargs=dict(stream_data=stream, notify_action='pause')).start() if any(d['on_pause'] for d in notifiers.available_notification_agents()):
# Push any notifications -
# Push it on it's own thread so we don't hold up our db actions
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=stream, notify_action='pause')).start()
if session['state'] == 'playing' and stream['state'] == 'paused': if session['state'] == 'playing' and stream['state'] == 'paused':
# Push any notifications - logger.debug(u"PlexPy Monitor :: Session %s has been resumed." % stream['session_key'])
# Push it on it's own thread so we don't hold up our db actions
threading.Thread(target=notification_handler.notify, # Check if any notification agents have notifications enabled
kwargs=dict(stream_data=stream, notify_action='resume')).start() if any(d['on_resume'] for d in notifiers.available_notification_agents()):
# Push any notifications -
# Push it on it's own thread so we don't hold up our db actions
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=stream, notify_action='resume')).start()
if stream['state'] == 'paused' and not ws_request: if stream['state'] == 'paused' and not ws_request:
# The stream is still paused so we need to increment the paused_counter # The stream is still paused so we need to increment the paused_counter
@ -105,8 +116,12 @@ def check_active_sessions(ws_request=False):
'WHERE session_key = ? AND rating_key = ?', 'WHERE session_key = ? AND rating_key = ?',
[stream['session_key'], stream['rating_key']]) [stream['session_key'], stream['rating_key']])
threading.Thread(target=notification_handler.notify, # Check if any notification agents have notifications enabled
kwargs=dict(stream_data=stream, notify_action='buffer')).start() if any(d['on_buffer'] for d in notifiers.available_notification_agents()):
# Push any notifications -
# Push it on it's own thread so we don't hold up our db actions
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=stream, notify_action='buffer')).start()
else: else:
# Subsequent buffer notifications after wait time # Subsequent buffer notifications after wait time
if int(time.time()) > buffer_values[0]['buffer_last_triggered'] + \ if int(time.time()) > buffer_values[0]['buffer_last_triggered'] + \
@ -119,11 +134,16 @@ def check_active_sessions(ws_request=False):
'WHERE session_key = ? AND rating_key = ?', 'WHERE session_key = ? AND rating_key = ?',
[stream['session_key'], stream['rating_key']]) [stream['session_key'], stream['rating_key']])
threading.Thread(target=notification_handler.notify, # Check if any notification agents have notifications enabled
kwargs=dict(stream_data=stream, notify_action='buffer')).start() if any(d['on_buffer'] for d in notifiers.available_notification_agents()):
# Push any notifications -
# Push it on it's own thread so we don't hold up our db actions
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=stream, notify_action='buffer')).start()
logger.debug(u"PlexPy Monitor :: Stream buffering. Count is now %s. Last triggered %s." logger.debug(u"PlexPy Monitor :: Session %s is buffering. Count is now %s. Last triggered %s."
% (buffer_values[0]['buffer_count'], % (stream['session_key'],
buffer_values[0]['buffer_count'],
buffer_values[0]['buffer_last_triggered'])) buffer_values[0]['buffer_last_triggered']))
# Check if the user has reached the offset in the media we defined as the "watched" percent # Check if the user has reached the offset in the media we defined as the "watched" percent
@ -132,37 +152,61 @@ def check_active_sessions(ws_request=False):
if session['view_offset'] and session['duration'] and session['state'] != 'buffering': if session['view_offset'] and session['duration'] and session['state'] != 'buffering':
if helpers.get_percent(session['view_offset'], if helpers.get_percent(session['view_offset'],
session['duration']) > plexpy.CONFIG.NOTIFY_WATCHED_PERCENT: session['duration']) > plexpy.CONFIG.NOTIFY_WATCHED_PERCENT:
# Check if any notification agents have notifications enabled
if any(d['on_watched'] for d in notifiers.available_notification_agents()):
# Push any notifications -
# Push it on it's own thread so we don't hold up our db actions
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=stream, notify_action='watched')).start()
else:
# The user has stopped playing a stream
if stream['state'] != 'stopped':
logger.debug(u"PlexPy Monitor :: Session %s has stopped." % stream['session_key'])
# Set the stream stop time
stream['stopped'] = int(time.time())
monitor_db.action('UPDATE sessions SET stopped = ?, state = ? '
'WHERE session_key = ? AND rating_key = ?',
[stream['stopped'], 'stopped', stream['session_key'], stream['rating_key']])
# Check if the user has reached the offset in the media we defined as the "watched" percent
if stream['view_offset'] and stream['duration']:
if helpers.get_percent(stream['view_offset'],
stream['duration']) > plexpy.CONFIG.NOTIFY_WATCHED_PERCENT:
# Check if any notification agents have notifications enabled
if any(d['on_watched'] for d in notifiers.available_notification_agents()):
# Push any notifications - # Push any notifications -
# Push it on it's own thread so we don't hold up our db actions # Push it on it's own thread so we don't hold up our db actions
threading.Thread(target=notification_handler.notify, threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=stream, notify_action='watched')).start() kwargs=dict(stream_data=stream, notify_action='watched')).start()
else: # Check if any notification agents have notifications enabled
# The user has stopped playing a stream if any(d['on_stop'] for d in notifiers.available_notification_agents()):
logger.debug(u"PlexPy Monitor :: Removing sessionKey %s ratingKey %s from session queue" # Push any notifications - Push it on it's own thread so we don't hold up our db actions
% (stream['session_key'], stream['rating_key']))
monitor_db.action('DELETE FROM sessions WHERE session_key = ? AND rating_key = ?',
[stream['session_key'], stream['rating_key']])
# Check if the user has reached the offset in the media we defined as the "watched" percent
if stream['view_offset'] and stream['duration']:
if helpers.get_percent(stream['view_offset'],
stream['duration']) > plexpy.CONFIG.NOTIFY_WATCHED_PERCENT:
# Push any notifications -
# Push it on it's own thread so we don't hold up our db actions
threading.Thread(target=notification_handler.notify, threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=stream, notify_action='watched')).start() kwargs=dict(stream_data=stream, notify_action='stop')).start()
# Push any notifications - Push it on it's own thread so we don't hold up our db actions
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=stream, notify_action='stop')).start()
# Write the item history on playback stop # Write the item history on playback stop
monitor_process.write_session_history(session=stream) success = monitor_process.write_session_history(session=stream)
if success:
# If session is written to the databaase successfully, remove the session from the session table
logger.debug(u"PlexPy Monitor :: Removing sessionKey %s ratingKey %s from session queue"
% (stream['session_key'], stream['rating_key']))
monitor_db.action('DELETE FROM sessions WHERE session_key = ? AND rating_key = ?',
[stream['session_key'], stream['rating_key']])
else:
logger.warn(u"PlexPy Monitor :: Failed to write sessionKey %s ratingKey %s to the database. " \
"Will try again on the next pass." % (stream['session_key'], stream['rating_key']))
# Process the newly received session data # Process the newly received session data
for session in media_container: for session in media_container:
monitor_process.write_session(session) new_session = monitor_process.write_session(session)
if new_session:
logger.debug(u"PlexPy Monitor :: Session %s has started." % session['session_key'])
else: else:
logger.debug(u"PlexPy Monitor :: Unable to read session list.") logger.debug(u"PlexPy Monitor :: Unable to read session list.")
@ -171,9 +215,11 @@ def check_active_sessions(ws_request=False):
% str(int_ping_count)) % str(int_ping_count))
if int_ping_count == 3: if int_ping_count == 3:
# Fire off notifications # Check if any notification agents have notifications enabled
threading.Thread(target=notification_handler.notify_timeline, if any(d['on_intdown'] for d in notifiers.available_notification_agents()):
kwargs=dict(notify_action='intdown')).start() # Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(notify_action='intdown')).start()
def check_recently_added(): def check_recently_added():
@ -225,9 +271,12 @@ def check_recently_added():
if 0 < time_threshold - int(item['added_at']) <= time_interval: if 0 < time_threshold - int(item['added_at']) <= time_interval:
logger.debug(u"PlexPy Monitor :: Library item %s has been added to Plex." % str(item['rating_key'])) logger.debug(u"PlexPy Monitor :: Library item %s has been added to Plex." % str(item['rating_key']))
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline, # Check if any notification agents have notifications enabled
kwargs=dict(timeline_data=item, notify_action='created')).start() if any(d['on_created'] for d in notifiers.available_notification_agents()):
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(timeline_data=item, notify_action='created')).start()
else: else:
item = max(metadata, key=lambda x:x['added_at']) item = max(metadata, key=lambda x:x['added_at'])
@ -243,9 +292,12 @@ def check_recently_added():
% str(item['rating_key'])) % str(item['rating_key']))
logger.debug(u"PlexPy Monitor :: Library item %s has been added to Plex." % str(item['rating_key'])) logger.debug(u"PlexPy Monitor :: Library item %s has been added to Plex." % str(item['rating_key']))
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline, # Check if any notification agents have notifications enabled
kwargs=dict(timeline_data=item, notify_action='created')).start() if any(d['on_created'] for d in notifiers.available_notification_agents()):
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(timeline_data=item, notify_action='created')).start()
def check_server_response(): def check_server_response():
@ -275,12 +327,44 @@ def check_server_response():
else: else:
if ext_ping_count >= 3: if ext_ping_count >= 3:
logger.info(u"PlexPy Monitor :: Plex remote access is back up.") logger.info(u"PlexPy Monitor :: Plex remote access is back up.")
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline, # Check if any notification agents have notifications enabled
kwargs=dict(notify_action='extup')).start() if any(d['on_extup'] for d in notifiers.available_notification_agents()):
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(notify_action='extup')).start()
ext_ping_count = 0 ext_ping_count = 0
if ext_ping_count == 3: if ext_ping_count == 3:
# Fire off notifications # Check if any notification agents have notifications enabled
threading.Thread(target=notification_handler.notify_timeline, if any(d['on_extdown'] for d in notifiers.available_notification_agents()):
kwargs=dict(notify_action='extdown')).start() # Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(notify_action='extdown')).start()
def check_server_updates():
with monitor_lock:
logger.info(u"PlexPy Monitor :: Checking for PMS updates...")
pms_connect = pmsconnect.PmsConnect()
server_identity = pms_connect.get_server_identity()
update_status = pms_connect.get_update_staus()
if server_identity and update_status:
version = server_identity['version']
logger.info(u"PlexPy Monitor :: Current PMS version: %s", version)
if update_status['state'] == 'available':
update_version = update_status['version']
logger.info(u"PlexPy Monitor :: PMS update available version: %s", update_version)
# Check if any notification agents have notifications enabled
if any(d['on_pmsupdate'] for d in notifiers.available_notification_agents()):
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(notify_action='pmsupdate')).start()
else:
logger.info(u"PlexPy Monitor :: No PMS update available.")

View file

@ -13,7 +13,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>. # along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
from plexpy import logger, pmsconnect, notification_handler, log_reader, database from plexpy import logger, pmsconnect, notification_handler, log_reader, database, notifiers
import threading import threading
import plexpy import plexpy
@ -78,9 +78,10 @@ class ActivityProcessor(object):
result = self.db.upsert('sessions', values, keys) result = self.db.upsert('sessions', values, keys)
if result == 'insert': if result == 'insert':
# Push any notifications - Push it on it's own thread so we don't hold up our db actions # Check if any notification agents have notifications enabled
if notify: if notify and any(d['on_play'] for d in notifiers.available_notification_agents()):
values.update({'ip_address': session['ip_address']}) values.update({'ip_address': session['ip_address']})
# Push any notifications - Push it on it's own thread so we don't hold up our db actions
threading.Thread(target=notification_handler.notify, threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=values, notify_action='play')).start() kwargs=dict(stream_data=values, notify_action='play')).start()
@ -97,16 +98,23 @@ class ActivityProcessor(object):
ip_address = {'ip_address': ip_address} ip_address = {'ip_address': ip_address}
self.db.upsert('sessions', ip_address, keys) self.db.upsert('sessions', ip_address, keys)
return True
def write_session_history(self, session=None, import_metadata=None, is_import=False, import_ignore_interval=0): def write_session_history(self, session=None, import_metadata=None, is_import=False, import_ignore_interval=0):
from plexpy import users, libraries from plexpy import users, libraries
user_data = users.Users()
user_details = user_data.get_details(user_id=session['user_id'])
section_id = session['section_id'] if not is_import else import_metadata['section_id'] section_id = session['section_id'] if not is_import else import_metadata['section_id']
library_data = libraries.Libraries() if not is_import:
library_details = library_data.get_details(section_id=section_id) user_data = users.Users()
user_details = user_data.get_details(user_id=session['user_id'])
library_data = libraries.Libraries()
library_details = library_data.get_details(section_id=section_id)
# Return false if failed to retrieve user or library details
if not user_details or not library_details:
return False
if session: if session:
logging_enabled = False logging_enabled = False
@ -116,8 +124,14 @@ class ActivityProcessor(object):
stopped = int(session['stopped']) stopped = int(session['stopped'])
else: else:
stopped = int(time.time()) stopped = int(time.time())
elif session['stopped']:
stopped = int(session['stopped'])
else: else:
stopped = int(time.time()) stopped = int(time.time())
self.set_session_state(session_key=session['session_key'],
state='stopped',
view_offset=session['viewOffset'],
stopped=stopped)
if plexpy.CONFIG.MOVIE_LOGGING_ENABLE and str(session['rating_key']).isdigit() and \ if plexpy.CONFIG.MOVIE_LOGGING_ENABLE and str(session['rating_key']).isdigit() and \
session['media_type'] == 'movie': session['media_type'] == 'movie':
@ -137,14 +151,14 @@ class ActivityProcessor(object):
else: else:
real_play_time = stopped - session['started'] real_play_time = stopped - session['started']
if plexpy.CONFIG.LOGGING_IGNORE_INTERVAL and not is_import: if not is_import and plexpy.CONFIG.LOGGING_IGNORE_INTERVAL:
if (session['media_type'] == 'movie' or session['media_type'] == 'episode') and \ if (session['media_type'] == 'movie' or session['media_type'] == 'episode') and \
(real_play_time < 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 ActivityProcessor :: Play duration for ratingKey %s is %s secs which is less than %s " logger.debug(u"PlexPy ActivityProcessor :: 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(real_play_time), plexpy.CONFIG.LOGGING_IGNORE_INTERVAL)) (session['rating_key'], str(real_play_time), plexpy.CONFIG.LOGGING_IGNORE_INTERVAL))
if session['media_type'] == 'track' and not is_import: if not is_import and session['media_type'] == 'track':
if real_play_time < 15 and session['duration'] >= 30: if real_play_time < 15 and session['duration'] >= 30:
logging_enabled = False logging_enabled = False
logger.debug(u"PlexPy ActivityProcessor :: Play duration for ratingKey %s is %s secs, " logger.debug(u"PlexPy ActivityProcessor :: Play duration for ratingKey %s is %s secs, "
@ -156,17 +170,29 @@ class ActivityProcessor(object):
logging_enabled = False logging_enabled = False
logger.debug(u"PlexPy ActivityProcessor :: Play duration for ratingKey %s is %s secs which is less than %s " logger.debug(u"PlexPy ActivityProcessor :: 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(real_play_time), (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 is_import and not user_details['keep_history']:
logging_enabled = False logging_enabled = False
logger.debug(u"PlexPy ActivityProcessor :: History logging for user '%s' is disabled." % user_details['username']) logger.debug(u"PlexPy ActivityProcessor :: History logging for user '%s' is disabled." % user_details['username'])
elif not library_details['keep_history'] and not is_import: elif not is_import and not library_details['keep_history']:
logging_enabled = False logging_enabled = False
logger.debug(u"PlexPy ActivityProcessor :: History logging for library '%s' is disabled." % library_details['section_name']) logger.debug(u"PlexPy ActivityProcessor :: History logging for library '%s' is disabled." % library_details['section_name'])
if logging_enabled: if logging_enabled:
# Fetch metadata first so we can return false if it fails
if not is_import:
logger.debug(u"PlexPy ActivityProcessor :: Fetching metadata for item ratingKey %s" % session['rating_key'])
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_metadata_details(rating_key=str(session['rating_key']))
if result:
metadata = result['metadata']
else:
return False
else:
metadata = import_metadata
# logger.debug(u"PlexPy ActivityProcessor :: Attempting to write to session_history table...") # logger.debug(u"PlexPy ActivityProcessor :: Attempting to write to session_history table...")
query = 'INSERT INTO session_history (started, stopped, rating_key, parent_rating_key, ' \ query = 'INSERT INTO session_history (started, stopped, rating_key, parent_rating_key, ' \
'grandparent_rating_key, media_type, user_id, user, ip_address, paused_counter, player, ' \ 'grandparent_rating_key, media_type, user_id, user, ip_address, paused_counter, player, ' \
@ -218,13 +244,22 @@ class ActivityProcessor(object):
# % last_id) # % last_id)
# Write the session_history_media_info table # Write the session_history_media_info table
# Generate a combined transcode decision value
if session['video_decision'] == 'transcode' or session['audio_decision'] == 'transcode':
transcode_decision = 'transcode'
elif session['video_decision'] == 'copy' or session['audio_decision'] == 'copy':
transcode_decision = 'copy'
else:
transcode_decision = 'direct play'
# logger.debug(u"PlexPy ActivityProcessor :: Attempting to write to session_history_media_info table...") # logger.debug(u"PlexPy ActivityProcessor :: Attempting to write to session_history_media_info table...")
query = 'INSERT INTO session_history_media_info (id, rating_key, video_decision, audio_decision, ' \ query = 'INSERT INTO session_history_media_info (id, rating_key, video_decision, audio_decision, ' \
'duration, width, height, container, video_codec, audio_codec, bitrate, video_resolution, ' \ 'duration, width, height, container, video_codec, audio_codec, bitrate, video_resolution, ' \
'video_framerate, aspect_ratio, audio_channels, transcode_protocol, transcode_container, ' \ 'video_framerate, aspect_ratio, audio_channels, transcode_protocol, transcode_container, ' \
'transcode_video_codec, transcode_audio_codec, transcode_audio_channels, transcode_width, ' \ 'transcode_video_codec, transcode_audio_codec, transcode_audio_channels, transcode_width, ' \
'transcode_height) VALUES ' \ 'transcode_height, transcode_decision) VALUES ' \
'(last_insert_rowid(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)' '(last_insert_rowid(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
args = [session['rating_key'], session['video_decision'], session['audio_decision'], args = [session['rating_key'], session['video_decision'], session['audio_decision'],
session['duration'], session['width'], session['height'], session['container'], session['duration'], session['width'], session['height'], session['container'],
@ -232,19 +267,12 @@ class ActivityProcessor(object):
session['video_resolution'], session['video_framerate'], session['aspect_ratio'], session['video_resolution'], session['video_framerate'], session['aspect_ratio'],
session['audio_channels'], session['transcode_protocol'], session['transcode_container'], session['audio_channels'], session['transcode_protocol'], session['transcode_container'],
session['transcode_video_codec'], session['transcode_audio_codec'], session['transcode_video_codec'], session['transcode_audio_codec'],
session['transcode_audio_channels'], session['transcode_width'], session['transcode_height']] session['transcode_audio_channels'], session['transcode_width'], session['transcode_height'],
transcode_decision]
# logger.debug(u"PlexPy ActivityProcessor :: Writing session_history_media_info transaction...") # logger.debug(u"PlexPy ActivityProcessor :: Writing session_history_media_info transaction...")
self.db.action(query=query, args=args) self.db.action(query=query, args=args)
if not is_import:
logger.debug(u"PlexPy ActivityProcessor :: Fetching metadata for item ratingKey %s" % session['rating_key'])
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_metadata_details(rating_key=str(session['rating_key']))
metadata = result['metadata']
else:
metadata = import_metadata
# Write the session_history_metadata table # Write the session_history_metadata table
directors = ";".join(metadata['directors']) directors = ";".join(metadata['directors'])
writers = ";".join(metadata['writers']) writers = ";".join(metadata['writers'])
@ -280,6 +308,9 @@ class ActivityProcessor(object):
# logger.debug(u"PlexPy ActivityProcessor :: Writing session_history_metadata transaction...") # logger.debug(u"PlexPy ActivityProcessor :: Writing session_history_metadata transaction...")
self.db.action(query=query, args=args) self.db.action(query=query, args=args)
# Return true when the session is successfully written to the database
return True
def find_session_ip(self, rating_key=None, machine_id=None): def find_session_ip(self, rating_key=None, machine_id=None):
logger.debug(u"PlexPy ActivityProcessor :: Requesting log lines...") logger.debug(u"PlexPy ActivityProcessor :: Requesting log lines...")
@ -351,12 +382,15 @@ class ActivityProcessor(object):
return None return None
def set_session_state(self, session_key=None, state=None, view_offset=0): def set_session_state(self, session_key=None, state=None, view_offset=0, **kwargs):
if str(session_key).isdigit() and str(view_offset).isdigit(): if str(session_key).isdigit() and str(view_offset).isdigit():
values = {'view_offset': int(view_offset)} values = {'view_offset': int(view_offset)}
if state: if state:
values['state'] = state values['state'] = state
for k,v in kwargs.iteritems():
values[k] = v
keys = {'session_key': session_key} keys = {'session_key': session_key}
result = self.db.upsert('sessions', values, keys) result = self.db.upsert('sessions', values, keys)

View file

@ -62,6 +62,7 @@ MEDIA_FLAGS_VIDEO = {'avc1': 'h264',
SCHEDULER_LIST = ['Check GitHub for updates', SCHEDULER_LIST = ['Check GitHub for updates',
'Check for active sessions', 'Check for active sessions',
'Check for recently added items', 'Check for recently added items',
'Check for Plex updates',
'Check for Plex remote access', 'Check for Plex remote access',
'Refresh users list', 'Refresh users list',
'Refresh libraries list', 'Refresh libraries list',

View file

@ -48,6 +48,7 @@ _CONFIG_DEFINITIONS = {
'BOXCAR_ON_INTDOWN': (int, 'Boxcar', 0), 'BOXCAR_ON_INTDOWN': (int, 'Boxcar', 0),
'BOXCAR_ON_EXTUP': (int, 'Boxcar', 0), 'BOXCAR_ON_EXTUP': (int, 'Boxcar', 0),
'BOXCAR_ON_INTUP': (int, 'Boxcar', 0), 'BOXCAR_ON_INTUP': (int, 'Boxcar', 0),
'BOXCAR_ON_PMSUPDATE': (int, 'Boxcar', 0),
'BUFFER_THRESHOLD': (int, 'Monitoring', 3), 'BUFFER_THRESHOLD': (int, 'Monitoring', 3),
'BUFFER_WAIT': (int, 'Monitoring', 900), 'BUFFER_WAIT': (int, 'Monitoring', 900),
'BACKUP_DIR': (str, 'General', ''), 'BACKUP_DIR': (str, 'General', ''),
@ -81,6 +82,7 @@ _CONFIG_DEFINITIONS = {
'EMAIL_ON_INTDOWN': (int, 'Email', 0), 'EMAIL_ON_INTDOWN': (int, 'Email', 0),
'EMAIL_ON_EXTUP': (int, 'Email', 0), 'EMAIL_ON_EXTUP': (int, 'Email', 0),
'EMAIL_ON_INTUP': (int, 'Email', 0), 'EMAIL_ON_INTUP': (int, 'Email', 0),
'EMAIL_ON_PMSUPDATE': (int, 'Email', 0),
'ENABLE_HTTPS': (int, 'General', 0), 'ENABLE_HTTPS': (int, 'General', 0),
'FACEBOOK_ENABLED': (int, 'Facebook', 0), 'FACEBOOK_ENABLED': (int, 'Facebook', 0),
'FACEBOOK_REDIRECT_URI': (str, 'Facebook', ''), 'FACEBOOK_REDIRECT_URI': (str, 'Facebook', ''),
@ -88,6 +90,7 @@ _CONFIG_DEFINITIONS = {
'FACEBOOK_APP_SECRET': (str, 'Facebook', ''), 'FACEBOOK_APP_SECRET': (str, 'Facebook', ''),
'FACEBOOK_TOKEN': (str, 'Facebook', ''), 'FACEBOOK_TOKEN': (str, 'Facebook', ''),
'FACEBOOK_GROUP': (str, 'Facebook', ''), 'FACEBOOK_GROUP': (str, 'Facebook', ''),
'FACEBOOK_INCL_PMSLINK': (int, 'Facebook', 0),
'FACEBOOK_INCL_POSTER': (int, 'Facebook', 1), 'FACEBOOK_INCL_POSTER': (int, 'Facebook', 1),
'FACEBOOK_INCL_SUBJECT': (int, 'Facebook', 1), 'FACEBOOK_INCL_SUBJECT': (int, 'Facebook', 1),
'FACEBOOK_ON_PLAY': (int, 'Facebook', 0), 'FACEBOOK_ON_PLAY': (int, 'Facebook', 0),
@ -101,6 +104,7 @@ _CONFIG_DEFINITIONS = {
'FACEBOOK_ON_INTDOWN': (int, 'Facebook', 0), 'FACEBOOK_ON_INTDOWN': (int, 'Facebook', 0),
'FACEBOOK_ON_EXTUP': (int, 'Facebook', 0), 'FACEBOOK_ON_EXTUP': (int, 'Facebook', 0),
'FACEBOOK_ON_INTUP': (int, 'Facebook', 0), 'FACEBOOK_ON_INTUP': (int, 'Facebook', 0),
'FACEBOOK_ON_PMSUPDATE': (int, 'Facebook', 0),
'FIRST_RUN_COMPLETE': (int, 'General', 0), 'FIRST_RUN_COMPLETE': (int, 'General', 0),
'FREEZE_DB': (int, 'General', 0), 'FREEZE_DB': (int, 'General', 0),
'GET_FILE_SIZES': (int, 'General', 0), 'GET_FILE_SIZES': (int, 'General', 0),
@ -126,6 +130,7 @@ _CONFIG_DEFINITIONS = {
'GROWL_ON_INTDOWN': (int, 'Growl', 0), 'GROWL_ON_INTDOWN': (int, 'Growl', 0),
'GROWL_ON_EXTUP': (int, 'Growl', 0), 'GROWL_ON_EXTUP': (int, 'Growl', 0),
'GROWL_ON_INTUP': (int, 'Growl', 0), 'GROWL_ON_INTUP': (int, 'Growl', 0),
'GROWL_ON_PMSUPDATE': (int, 'Growl', 0),
'HOME_LIBRARY_CARDS': (list, 'General', ['first_run']), 'HOME_LIBRARY_CARDS': (list, 'General', ['first_run']),
'HOME_STATS_LENGTH': (int, 'General', 30), 'HOME_STATS_LENGTH': (int, 'General', 30),
'HOME_STATS_TYPE': (int, 'General', 0), 'HOME_STATS_TYPE': (int, 'General', 0),
@ -159,6 +164,7 @@ _CONFIG_DEFINITIONS = {
'IFTTT_ON_INTDOWN': (int, 'IFTTT', 0), 'IFTTT_ON_INTDOWN': (int, 'IFTTT', 0),
'IFTTT_ON_EXTUP': (int, 'IFTTT', 0), 'IFTTT_ON_EXTUP': (int, 'IFTTT', 0),
'IFTTT_ON_INTUP': (int, 'IFTTT', 0), 'IFTTT_ON_INTUP': (int, 'IFTTT', 0),
'IFTTT_ON_PMSUPDATE': (int, 'IFTTT', 0),
'JOURNAL_MODE': (str, 'Advanced', 'wal'), 'JOURNAL_MODE': (str, 'Advanced', 'wal'),
'LAUNCH_BROWSER': (int, 'General', 1), 'LAUNCH_BROWSER': (int, 'General', 1),
'LOG_DIR': (str, 'General', ''), 'LOG_DIR': (str, 'General', ''),
@ -173,6 +179,7 @@ _CONFIG_DEFINITIONS = {
'MUSIC_NOTIFY_ON_START': (int, 'Monitoring', 1), 'MUSIC_NOTIFY_ON_START': (int, 'Monitoring', 1),
'MUSIC_NOTIFY_ON_STOP': (int, 'Monitoring', 0), 'MUSIC_NOTIFY_ON_STOP': (int, 'Monitoring', 0),
'MUSIC_NOTIFY_ON_PAUSE': (int, 'Monitoring', 0), 'MUSIC_NOTIFY_ON_PAUSE': (int, 'Monitoring', 0),
'MONITOR_PMS_UPDATES': (int, 'Monitoring', 0),
'MONITOR_REMOTE_ACCESS': (int, 'Monitoring', 0), 'MONITOR_REMOTE_ACCESS': (int, 'Monitoring', 0),
'MONITORING_INTERVAL': (int, 'Monitoring', 60), 'MONITORING_INTERVAL': (int, 'Monitoring', 60),
'MONITORING_USE_WEBSOCKET': (int, 'Monitoring', 0), 'MONITORING_USE_WEBSOCKET': (int, 'Monitoring', 0),
@ -190,6 +197,7 @@ _CONFIG_DEFINITIONS = {
'NMA_ON_INTDOWN': (int, 'NMA', 0), 'NMA_ON_INTDOWN': (int, 'NMA', 0),
'NMA_ON_EXTUP': (int, 'NMA', 0), 'NMA_ON_EXTUP': (int, 'NMA', 0),
'NMA_ON_INTUP': (int, 'NMA', 0), 'NMA_ON_INTUP': (int, 'NMA', 0),
'NMA_ON_PMSUPDATE': (int, 'NMA', 0),
'NOTIFY_CONSECUTIVE': (int, 'Monitoring', 1), 'NOTIFY_CONSECUTIVE': (int, 'Monitoring', 1),
'NOTIFY_UPLOAD_POSTERS': (int, 'Monitoring', 0), 'NOTIFY_UPLOAD_POSTERS': (int, 'Monitoring', 0),
'NOTIFY_RECENTLY_ADDED': (int, 'Monitoring', 0), 'NOTIFY_RECENTLY_ADDED': (int, 'Monitoring', 0),
@ -218,6 +226,8 @@ _CONFIG_DEFINITIONS = {
'NOTIFY_ON_EXTUP_BODY_TEXT': (unicode, 'Monitoring', 'The Plex Media Server remote access is back up.'), 'NOTIFY_ON_EXTUP_BODY_TEXT': (unicode, 'Monitoring', 'The Plex Media Server remote access is back up.'),
'NOTIFY_ON_INTUP_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'), 'NOTIFY_ON_INTUP_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'),
'NOTIFY_ON_INTUP_BODY_TEXT': (unicode, 'Monitoring', 'The Plex Media Server is back up.'), 'NOTIFY_ON_INTUP_BODY_TEXT': (unicode, 'Monitoring', 'The Plex Media Server is back up.'),
'NOTIFY_ON_PMSUPDATE_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'),
'NOTIFY_ON_PMSUPDATE_BODY_TEXT': (unicode, 'Monitoring', 'An update is available for the Plex Media Server (version {update_version}).'),
'NOTIFY_SCRIPTS_ARGS_TEXT': (unicode, 'Monitoring', ''), 'NOTIFY_SCRIPTS_ARGS_TEXT': (unicode, 'Monitoring', ''),
'OSX_NOTIFY_APP': (str, 'OSX_Notify', '/Applications/PlexPy'), 'OSX_NOTIFY_APP': (str, 'OSX_Notify', '/Applications/PlexPy'),
'OSX_NOTIFY_ENABLED': (int, 'OSX_Notify', 0), 'OSX_NOTIFY_ENABLED': (int, 'OSX_Notify', 0),
@ -232,6 +242,7 @@ _CONFIG_DEFINITIONS = {
'OSX_NOTIFY_ON_INTDOWN': (int, 'OSX_Notify', 0), 'OSX_NOTIFY_ON_INTDOWN': (int, 'OSX_Notify', 0),
'OSX_NOTIFY_ON_EXTUP': (int, 'OSX_Notify', 0), 'OSX_NOTIFY_ON_EXTUP': (int, 'OSX_Notify', 0),
'OSX_NOTIFY_ON_INTUP': (int, 'OSX_Notify', 0), 'OSX_NOTIFY_ON_INTUP': (int, 'OSX_Notify', 0),
'OSX_NOTIFY_ON_PMSUPDATE': (int, 'OSX_Notify', 0),
'PLEX_CLIENT_HOST': (str, 'Plex', ''), 'PLEX_CLIENT_HOST': (str, 'Plex', ''),
'PLEX_ENABLED': (int, 'Plex', 0), 'PLEX_ENABLED': (int, 'Plex', 0),
'PLEX_PASSWORD': (str, 'Plex', ''), 'PLEX_PASSWORD': (str, 'Plex', ''),
@ -247,6 +258,7 @@ _CONFIG_DEFINITIONS = {
'PLEX_ON_INTDOWN': (int, 'Plex', 0), 'PLEX_ON_INTDOWN': (int, 'Plex', 0),
'PLEX_ON_EXTUP': (int, 'Plex', 0), 'PLEX_ON_EXTUP': (int, 'Plex', 0),
'PLEX_ON_INTUP': (int, 'Plex', 0), 'PLEX_ON_INTUP': (int, 'Plex', 0),
'PLEX_ON_PMSUPDATE': (int, 'Plex', 0),
'PROWL_ENABLED': (int, 'Prowl', 0), 'PROWL_ENABLED': (int, 'Prowl', 0),
'PROWL_KEYS': (str, 'Prowl', ''), 'PROWL_KEYS': (str, 'Prowl', ''),
'PROWL_PRIORITY': (int, 'Prowl', 0), 'PROWL_PRIORITY': (int, 'Prowl', 0),
@ -261,6 +273,7 @@ _CONFIG_DEFINITIONS = {
'PROWL_ON_INTDOWN': (int, 'Prowl', 0), 'PROWL_ON_INTDOWN': (int, 'Prowl', 0),
'PROWL_ON_EXTUP': (int, 'Prowl', 0), 'PROWL_ON_EXTUP': (int, 'Prowl', 0),
'PROWL_ON_INTUP': (int, 'Prowl', 0), 'PROWL_ON_INTUP': (int, 'Prowl', 0),
'PROWL_ON_PMSUPDATE': (int, 'Prowl', 0),
'PUSHALOT_APIKEY': (str, 'Pushalot', ''), 'PUSHALOT_APIKEY': (str, 'Pushalot', ''),
'PUSHALOT_ENABLED': (int, 'Pushalot', 0), 'PUSHALOT_ENABLED': (int, 'Pushalot', 0),
'PUSHALOT_ON_PLAY': (int, 'Pushalot', 0), 'PUSHALOT_ON_PLAY': (int, 'Pushalot', 0),
@ -274,6 +287,7 @@ _CONFIG_DEFINITIONS = {
'PUSHALOT_ON_INTDOWN': (int, 'Pushalot', 0), 'PUSHALOT_ON_INTDOWN': (int, 'Pushalot', 0),
'PUSHALOT_ON_EXTUP': (int, 'Pushalot', 0), 'PUSHALOT_ON_EXTUP': (int, 'Pushalot', 0),
'PUSHALOT_ON_INTUP': (int, 'Pushalot', 0), 'PUSHALOT_ON_INTUP': (int, 'Pushalot', 0),
'PUSHALOT_ON_PMSUPDATE': (int, 'Pushalot', 0),
'PUSHBULLET_APIKEY': (str, 'PushBullet', ''), 'PUSHBULLET_APIKEY': (str, 'PushBullet', ''),
'PUSHBULLET_DEVICEID': (str, 'PushBullet', ''), 'PUSHBULLET_DEVICEID': (str, 'PushBullet', ''),
'PUSHBULLET_CHANNEL_TAG': (str, 'PushBullet', ''), 'PUSHBULLET_CHANNEL_TAG': (str, 'PushBullet', ''),
@ -289,6 +303,7 @@ _CONFIG_DEFINITIONS = {
'PUSHBULLET_ON_INTDOWN': (int, 'PushBullet', 0), 'PUSHBULLET_ON_INTDOWN': (int, 'PushBullet', 0),
'PUSHBULLET_ON_EXTUP': (int, 'PushBullet', 0), 'PUSHBULLET_ON_EXTUP': (int, 'PushBullet', 0),
'PUSHBULLET_ON_INTUP': (int, 'PushBullet', 0), 'PUSHBULLET_ON_INTUP': (int, 'PushBullet', 0),
'PUSHBULLET_ON_PMSUPDATE': (int, 'PushBullet', 0),
'PUSHOVER_APITOKEN': (str, 'Pushover', ''), 'PUSHOVER_APITOKEN': (str, 'Pushover', ''),
'PUSHOVER_ENABLED': (int, 'Pushover', 0), 'PUSHOVER_ENABLED': (int, 'Pushover', 0),
'PUSHOVER_HTML_SUPPORT': (int, 'Pushover', 1), 'PUSHOVER_HTML_SUPPORT': (int, 'Pushover', 1),
@ -306,6 +321,7 @@ _CONFIG_DEFINITIONS = {
'PUSHOVER_ON_INTDOWN': (int, 'Pushover', 0), 'PUSHOVER_ON_INTDOWN': (int, 'Pushover', 0),
'PUSHOVER_ON_EXTUP': (int, 'Pushover', 0), 'PUSHOVER_ON_EXTUP': (int, 'Pushover', 0),
'PUSHOVER_ON_INTUP': (int, 'Pushover', 0), 'PUSHOVER_ON_INTUP': (int, 'Pushover', 0),
'PUSHOVER_ON_PMSUPDATE': (int, 'Pushover', 0),
'REFRESH_LIBRARIES_INTERVAL': (int, 'Monitoring', 12), 'REFRESH_LIBRARIES_INTERVAL': (int, 'Monitoring', 12),
'REFRESH_LIBRARIES_ON_STARTUP': (int, 'Monitoring', 1), 'REFRESH_LIBRARIES_ON_STARTUP': (int, 'Monitoring', 1),
'REFRESH_USERS_INTERVAL': (int, 'Monitoring', 12), 'REFRESH_USERS_INTERVAL': (int, 'Monitoring', 12),
@ -327,6 +343,7 @@ _CONFIG_DEFINITIONS = {
'SLACK_ON_INTDOWN': (int, 'Slack', 0), 'SLACK_ON_INTDOWN': (int, 'Slack', 0),
'SLACK_ON_EXTUP': (int, 'Slack', 0), 'SLACK_ON_EXTUP': (int, 'Slack', 0),
'SLACK_ON_INTUP': (int, 'Slack', 0), 'SLACK_ON_INTUP': (int, 'Slack', 0),
'SLACK_ON_PMSUPDATE': (int, 'Slack', 0),
'SCRIPTS_ENABLED': (int, 'Scripts', 0), 'SCRIPTS_ENABLED': (int, 'Scripts', 0),
'SCRIPTS_FOLDER': (unicode, 'Scripts', ''), 'SCRIPTS_FOLDER': (unicode, 'Scripts', ''),
'SCRIPTS_ON_PLAY': (int, 'Scripts', 0), 'SCRIPTS_ON_PLAY': (int, 'Scripts', 0),
@ -340,6 +357,7 @@ _CONFIG_DEFINITIONS = {
'SCRIPTS_ON_EXTUP': (int, 'Scripts', 0), 'SCRIPTS_ON_EXTUP': (int, 'Scripts', 0),
'SCRIPTS_ON_INTDOWN': (int, 'Scripts', 0), 'SCRIPTS_ON_INTDOWN': (int, 'Scripts', 0),
'SCRIPTS_ON_INTUP': (int, 'Scripts', 0), 'SCRIPTS_ON_INTUP': (int, 'Scripts', 0),
'SCRIPTS_ON_PMSUPDATE': (int, 'Scripts', 0),
'SCRIPTS_ON_PLAY_SCRIPT': (unicode, 'Scripts', ''), 'SCRIPTS_ON_PLAY_SCRIPT': (unicode, 'Scripts', ''),
'SCRIPTS_ON_STOP_SCRIPT': (unicode, 'Scripts', ''), 'SCRIPTS_ON_STOP_SCRIPT': (unicode, 'Scripts', ''),
'SCRIPTS_ON_PAUSE_SCRIPT': (unicode, 'Scripts', ''), 'SCRIPTS_ON_PAUSE_SCRIPT': (unicode, 'Scripts', ''),
@ -351,6 +369,7 @@ _CONFIG_DEFINITIONS = {
'SCRIPTS_ON_EXTUP_SCRIPT': (unicode, 'Scripts', ''), 'SCRIPTS_ON_EXTUP_SCRIPT': (unicode, 'Scripts', ''),
'SCRIPTS_ON_INTDOWN_SCRIPT': (unicode, 'Scripts', ''), 'SCRIPTS_ON_INTDOWN_SCRIPT': (unicode, 'Scripts', ''),
'SCRIPTS_ON_INTUP_SCRIPT': (unicode, 'Scripts', ''), 'SCRIPTS_ON_INTUP_SCRIPT': (unicode, 'Scripts', ''),
'SCRIPTS_ON_PMSUPDATE_SCRIPT': (unicode, 'Scripts', ''),
'TELEGRAM_BOT_TOKEN': (str, 'Telegram', ''), 'TELEGRAM_BOT_TOKEN': (str, 'Telegram', ''),
'TELEGRAM_ENABLED': (int, 'Telegram', 0), 'TELEGRAM_ENABLED': (int, 'Telegram', 0),
'TELEGRAM_CHAT_ID': (str, 'Telegram', ''), 'TELEGRAM_CHAT_ID': (str, 'Telegram', ''),
@ -366,6 +385,7 @@ _CONFIG_DEFINITIONS = {
'TELEGRAM_ON_INTDOWN': (int, 'Telegram', 0), 'TELEGRAM_ON_INTDOWN': (int, 'Telegram', 0),
'TELEGRAM_ON_EXTUP': (int, 'Telegram', 0), 'TELEGRAM_ON_EXTUP': (int, 'Telegram', 0),
'TELEGRAM_ON_INTUP': (int, 'Telegram', 0), 'TELEGRAM_ON_INTUP': (int, 'Telegram', 0),
'TELEGRAM_ON_PMSUPDATE': (int, 'Telegram', 0),
'TV_LOGGING_ENABLE': (int, 'Monitoring', 1), 'TV_LOGGING_ENABLE': (int, 'Monitoring', 1),
'TV_NOTIFY_ENABLE': (int, 'Monitoring', 0), 'TV_NOTIFY_ENABLE': (int, 'Monitoring', 0),
'TV_NOTIFY_ON_START': (int, 'Monitoring', 1), 'TV_NOTIFY_ON_START': (int, 'Monitoring', 1),
@ -388,6 +408,7 @@ _CONFIG_DEFINITIONS = {
'TWITTER_ON_INTDOWN': (int, 'Twitter', 0), 'TWITTER_ON_INTDOWN': (int, 'Twitter', 0),
'TWITTER_ON_EXTUP': (int, 'Twitter', 0), 'TWITTER_ON_EXTUP': (int, 'Twitter', 0),
'TWITTER_ON_INTUP': (int, 'Twitter', 0), 'TWITTER_ON_INTUP': (int, 'Twitter', 0),
'TWITTER_ON_PMSUPDATE': (int, 'Twitter', 0),
'UPDATE_DB_INTERVAL': (int, 'General', 24), 'UPDATE_DB_INTERVAL': (int, 'General', 24),
'UPDATE_SECTION_IDS': (int, 'General', 1), 'UPDATE_SECTION_IDS': (int, 'General', 1),
'VERIFY_SSL_CERT': (bool_int, 'Advanced', 1), 'VERIFY_SSL_CERT': (bool_int, 'Advanced', 1),
@ -406,7 +427,8 @@ _CONFIG_DEFINITIONS = {
'XBMC_ON_EXTDOWN': (int, 'XBMC', 0), 'XBMC_ON_EXTDOWN': (int, 'XBMC', 0),
'XBMC_ON_INTDOWN': (int, 'XBMC', 0), 'XBMC_ON_INTDOWN': (int, 'XBMC', 0),
'XBMC_ON_EXTUP': (int, 'XBMC', 0), 'XBMC_ON_EXTUP': (int, 'XBMC', 0),
'XBMC_ON_INTUP': (int, 'XBMC', 0) 'XBMC_ON_INTUP': (int, 'XBMC', 0),
'XBMC_ON_PMSUPDATE': (int, 'XBMC', 0)
} }

View file

@ -18,6 +18,7 @@ import os
import sqlite3 import sqlite3
import shutil import shutil
import threading import threading
import time
import logger import logger
import plexpy import plexpy
@ -37,9 +38,21 @@ def clear_history_tables():
monitor_db.action('DELETE FROM session_history') monitor_db.action('DELETE FROM session_history')
monitor_db.action('DELETE FROM session_history_media_info') monitor_db.action('DELETE FROM session_history_media_info')
monitor_db.action('DELETE FROM session_history_metadata') monitor_db.action('DELETE FROM session_history_metadata')
monitor_db.action('VACUUM;') monitor_db.action('VACUUM')
def delete_sessions():
logger.debug(u"PlexPy Database :: Clearing temporary sessions from database.")
monitor_db = MonitorDatabase()
try:
monitor_db.action('DELETE FROM sessions')
monitor_db.action('VACUUM')
return 'Cleared temporary sessions.'
except Exception as e:
logger.warn(u"PlexPy Database :: Unable to clear temporary sessions from database: %s." % e)
return 'Unable to clear temporary sessions.'
def db_filename(filename="plexpy.db"): def db_filename(filename="plexpy.db"):
""" Returns the filepath to the db """ """ Returns the filepath to the db """

View file

@ -58,11 +58,10 @@ class DataFactory(object):
'session_history_metadata.thumb', 'session_history_metadata.thumb',
'session_history_metadata.parent_thumb', 'session_history_metadata.parent_thumb',
'session_history_metadata.grandparent_thumb', 'session_history_metadata.grandparent_thumb',
'MAX((CASE WHEN view_offset IS NULL THEN 0.1 ELSE view_offset * 1.0 END) / \ 'MAX((CASE WHEN (view_offset IS NULL OR view_offset = "") THEN 0.1 ELSE view_offset * 1.0 END) / \
(CASE WHEN session_history_metadata.duration IS NULL THEN 1.0 \ (CASE WHEN (session_history_metadata.duration IS NULL OR session_history_metadata.duration = "") \
ELSE session_history_metadata.duration * 1.0 END) * 100) AS percent_complete', THEN 1.0 ELSE session_history_metadata.duration * 1.0 END) * 100) AS percent_complete',
'session_history_media_info.video_decision', 'session_history_media_info.transcode_decision',
'session_history_media_info.audio_decision',
'COUNT(*) AS group_count', 'COUNT(*) AS group_count',
'GROUP_CONCAT(session_history.id) AS group_ids' 'GROUP_CONCAT(session_history.id) AS group_ids'
] ]
@ -138,8 +137,7 @@ class DataFactory(object):
'media_index': item['media_index'], 'media_index': item['media_index'],
'parent_media_index': item['parent_media_index'], 'parent_media_index': item['parent_media_index'],
'thumb': thumb, 'thumb': thumb,
'video_decision': item['video_decision'], 'transcode_decision': item['transcode_decision'],
'audio_decision': item['audio_decision'],
'percent_complete': int(round(item['percent_complete'])), 'percent_complete': int(round(item['percent_complete'])),
'watched_status': watched_status, 'watched_status': watched_status,
'group_count': item['group_count'], 'group_count': item['group_count'],
@ -626,24 +624,21 @@ class DataFactory(object):
title = 'Concurrent Transcodes' title = 'Concurrent Transcodes'
query = base_query \ query = base_query \
+ 'AND (session_history_media_info.video_decision = "transcode" ' \ + 'AND session_history_media_info.transcode_decision = "transcode" '
'OR session_history_media_info.audio_decision = "transcode") '
result = monitor_db.select(query) result = monitor_db.select(query)
if result: if result:
most_concurrent.append(calc_most_concurrent(title, result)) most_concurrent.append(calc_most_concurrent(title, result))
title = 'Concurrent Direct Streams' title = 'Concurrent Direct Streams'
query = base_query \ query = base_query \
+ 'AND (session_history_media_info.video_decision != "transcode" ' \ + 'AND session_history_media_info.transcode_decision = "copy" '
'AND session_history_media_info.audio_decision = "copy") '
result = monitor_db.select(query) result = monitor_db.select(query)
if result: if result:
most_concurrent.append(calc_most_concurrent(title, result)) most_concurrent.append(calc_most_concurrent(title, result))
title = 'Concurrent Direct Plays' title = 'Concurrent Direct Plays'
query = base_query \ query = base_query \
+ 'AND (session_history_media_info.video_decision = "direct play" ' \ + 'AND session_history_media_info.transcode_decision = "direct play" '
'OR session_history_media_info.audio_decision = "direct play") '
result = monitor_db.select(query) result = monitor_db.select(query)
if result: if result:
most_concurrent.append(calc_most_concurrent(title, result)) most_concurrent.append(calc_most_concurrent(title, result))
@ -828,6 +823,7 @@ class DataFactory(object):
'SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS total_duration ' \ 'SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS total_duration ' \
'FROM session_history ' \ 'FROM session_history ' \
'JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \ 'JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \
'JOIN session_history_media_info ON session_history_media_info.id = session_history.id ' \
'%s ' % where '%s ' % where
result = monitor_db.select(query) result = monitor_db.select(query)
except Exception as e: except Exception as e:
@ -860,16 +856,28 @@ class DataFactory(object):
return ip_address return ip_address
def get_poster_url(self, rating_key=''): def get_poster_url(self, rating_key='', metadata=None):
monitor_db = database.MonitorDatabase() monitor_db = database.MonitorDatabase()
poster_url = '' poster_url = ''
poster_key = ''
if rating_key: if rating_key:
poster_key = rating_key
elif metadata:
if metadata['media_type'] == 'movie' or metadata['media_type'] == 'show' or \
metadata['media_type'] == 'artist' or metadata['media_type'] == 'album':
poster_key = metadata['rating_key']
elif metadata['media_type'] == 'episode':
poster_key = metadata['grandparent_rating_key']
elif metadata['media_type'] == 'season' or metadata['media_type'] == 'track':
poster_key = metadata['parent_rating_key']
if poster_key:
try: try:
query = 'SELECT id, poster_url FROM notify_log ' \ query = 'SELECT id, poster_url FROM notify_log ' \
'WHERE rating_key = %d OR parent_rating_key = %d OR grandparent_rating_key = %d ' \ 'WHERE rating_key = %d OR parent_rating_key = %d OR grandparent_rating_key = %d ' \
'ORDER BY id DESC LIMIT 1' % (int(rating_key), int(rating_key), int(rating_key)) 'ORDER BY id DESC LIMIT 1' % (int(poster_key), int(poster_key), int(poster_key))
result = monitor_db.select(query) result = monitor_db.select(query)
except Exception as e: except Exception as e:
logger.warn(u"PlexPy DataFactory :: Unable to execute database query for get_poster_url: %s." % e) logger.warn(u"PlexPy DataFactory :: Unable to execute database query for get_poster_url: %s." % e)
@ -882,6 +890,16 @@ class DataFactory(object):
return poster_url return poster_url
def delete_poster_url(self, poster_url=''):
monitor_db = database.MonitorDatabase()
if poster_url:
logger.info(u"PlexPy DataFactory :: Deleting poster_url %s from the notify log database." % poster_url)
monitor_db.upsert('notify_log', {'poster_url': None}, {'poster_url': poster_url})
return 'Deleted poster_url %s.' % poster_url
else:
return 'Unable to delete poster_url.'
def get_search_query(self, rating_key=''): def get_search_query(self, rating_key=''):
monitor_db = database.MonitorDatabase() monitor_db = database.MonitorDatabase()
@ -1189,3 +1207,15 @@ class DataFactory(object):
} }
return dict return dict
def delete_notification_log(self):
monitor_db = database.MonitorDatabase()
try:
logger.info(u"PlexPy DataFactory :: Clearing notification logs from database.")
monitor_db.action('DELETE FROM notify_log')
monitor_db.action('VACUUM')
return 'Cleared notification logs.'
except Exception as e:
logger.warn(u"PlexPy DataFactory :: Unable to execute database query for delete_notification_log: %s." % e)
return 'Unable to clear notification logs.'

View file

@ -490,40 +490,39 @@ class Graphs(object):
try: try:
if y_axis == 'plays': if y_axis == 'plays':
query = 'SELECT date(session_history.started, "unixepoch", "localtime") AS date_played, ' \ query = 'SELECT date(session_history.started, "unixepoch", "localtime") AS date_played, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") THEN 1 ELSE 0 END) AS dp_count, ' \ 'THEN 1 ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
'AND session_history_media_info.audio_decision = "copy") THEN 1 ELSE 0 END) AS ds_count, ' \ 'THEN 1 ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'OR session_history_media_info.audio_decision = "transcode") THEN 1 ELSE 0 END) AS tc_count ' \ 'THEN 1 ELSE 0 END) AS tc_count ' \
'FROM session_history ' \ 'FROM session_history ' \
'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \ 'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \
'WHERE (datetime(session_history.stopped, "unixepoch", "localtime") >= ' \ 'WHERE (datetime(session_history.stopped, "unixepoch", "localtime") >= ' \
'datetime("now", "-%s days", "localtime")) AND ' \ 'datetime("now", "-%s days", "localtime")) AND ' \
'(session_history.media_type = "episode" OR session_history.media_type = "movie" OR session_history.media_type = "track") ' \ '(session_history.media_type = "episode" OR session_history.media_type = "movie" OR ' \
'session_history.media_type = "track") ' \
'GROUP BY date_played ' \ 'GROUP BY date_played ' \
'ORDER BY started ASC' % time_range 'ORDER BY started ASC' % time_range
result = monitor_db.select(query) result = monitor_db.select(query)
else: else:
query = 'SELECT date(session_history.started, "unixepoch", "localtime") AS date_played, ' \ query = 'SELECT date(session_history.started, "unixepoch", "localtime") AS date_played, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS dp_count, ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
'AND session_history_media_info.audio_decision = "copy") ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS ds_count, ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'OR session_history_media_info.audio_decision = "transcode") ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tc_count ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tc_count ' \
'FROM session_history ' \ 'FROM session_history ' \
'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \ 'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") >= ' \ 'WHERE datetime(session_history.stopped, "unixepoch", "localtime") >= ' \
'datetime("now", "-%s days", "localtime") AND ' \ 'datetime("now", "-%s days", "localtime") AND ' \
'(session_history.media_type = "episode" OR session_history.media_type = "movie" OR session_history.media_type = "track") ' \ '(session_history.media_type = "episode" OR session_history.media_type = "movie" OR ' \
'session_history.media_type = "track") ' \
'GROUP BY date_played ' \ 'GROUP BY date_played ' \
'ORDER BY started ASC' % time_range 'ORDER BY started ASC' % time_range
@ -583,12 +582,12 @@ class Graphs(object):
try: try:
if y_axis == 'plays': if y_axis == 'plays':
query = 'SELECT session_history_media_info.video_resolution AS resolution, ' \ query = 'SELECT session_history_media_info.video_resolution AS resolution, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") THEN 1 ELSE 0 END) AS dp_count, ' \ 'THEN 1 ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
'AND session_history_media_info.audio_decision = "copy") THEN 1 ELSE 0 END) AS ds_count, ' \ 'THEN 1 ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'OR session_history_media_info.audio_decision = "transcode") THEN 1 ELSE 0 END) AS tc_count, ' \ 'THEN 1 ELSE 0 END) AS tc_count, ' \
'COUNT(session_history.id) AS total_count ' \ 'COUNT(session_history.id) AS total_count ' \
'FROM session_history ' \ 'FROM session_history ' \
'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \ 'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \
@ -602,16 +601,13 @@ class Graphs(object):
result = monitor_db.select(query) result = monitor_db.select(query)
else: else:
query = 'SELECT session_history_media_info.video_resolution AS resolution,' \ query = 'SELECT session_history_media_info.video_resolution AS resolution,' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS dp_count, ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
'AND session_history_media_info.audio_decision = "copy") ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS ds_count, ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'OR session_history_media_info.audio_decision = "transcode") ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tc_count, ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tc_count, ' \
'SUM(CASE WHEN stopped > 0 THEN (stopped - started) ' \ 'SUM(CASE WHEN stopped > 0 THEN (stopped - started) ' \
@ -671,12 +667,12 @@ class Graphs(object):
'WHEN session_history_media_info.transcode_height <= 1440 THEN "QHD" ' \ 'WHEN session_history_media_info.transcode_height <= 1440 THEN "QHD" ' \
'WHEN session_history_media_info.transcode_height <= 2160 THEN "4K" ' \ 'WHEN session_history_media_info.transcode_height <= 2160 THEN "4K" ' \
'ELSE "unknown" END) ELSE session_history_media_info.video_resolution END) AS resolution, ' \ 'ELSE "unknown" END) ELSE session_history_media_info.video_resolution END) AS resolution, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") THEN 1 ELSE 0 END) AS dp_count, ' \ 'THEN 1 ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
'AND session_history_media_info.audio_decision = "copy") THEN 1 ELSE 0 END) AS ds_count, ' \ 'THEN 1 ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" '\ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" '\
'OR session_history_media_info.audio_decision = "transcode") THEN 1 ELSE 0 END) AS tc_count, ' \ 'THEN 1 ELSE 0 END) AS tc_count, ' \
'COUNT(session_history.id) AS total_count ' \ 'COUNT(session_history.id) AS total_count ' \
'FROM session_history ' \ 'FROM session_history ' \
'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \ 'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \
@ -700,16 +696,13 @@ class Graphs(object):
'WHEN session_history_media_info.transcode_height <= 1440 THEN "QHD" ' \ 'WHEN session_history_media_info.transcode_height <= 1440 THEN "QHD" ' \
'WHEN session_history_media_info.transcode_height <= 2160 THEN "4K" ' \ 'WHEN session_history_media_info.transcode_height <= 2160 THEN "4K" ' \
'ELSE "unknown" END) ELSE session_history_media_info.video_resolution END) AS resolution, ' \ 'ELSE "unknown" END) ELSE session_history_media_info.video_resolution END) AS resolution, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS dp_count, ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
'AND session_history_media_info.audio_decision = "copy") ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS ds_count, ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'OR session_history_media_info.audio_decision = "transcode") ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tc_count, ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tc_count, ' \
'SUM(CASE WHEN stopped > 0 THEN (stopped - started) ' \ 'SUM(CASE WHEN stopped > 0 THEN (stopped - started) ' \
@ -759,12 +752,12 @@ class Graphs(object):
try: try:
if y_axis == 'plays': if y_axis == 'plays':
query = 'SELECT session_history.platform AS platform, ' \ query = 'SELECT session_history.platform AS platform, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") THEN 1 ELSE 0 END) AS dp_count, ' \ 'THEN 1 ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
'AND session_history_media_info.audio_decision = "copy") THEN 1 ELSE 0 END) AS ds_count, ' \ 'THEN 1 ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'OR session_history_media_info.audio_decision = "transcode") THEN 1 ELSE 0 END) AS tc_count, ' \ 'THEN 1 ELSE 0 END) AS tc_count, ' \
'COUNT(session_history.id) AS total_count ' \ 'COUNT(session_history.id) AS total_count ' \
'FROM session_history ' \ 'FROM session_history ' \
'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \ 'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \
@ -777,16 +770,13 @@ class Graphs(object):
result = monitor_db.select(query) result = monitor_db.select(query)
else: else:
query = 'SELECT session_history.platform AS platform, ' \ query = 'SELECT session_history.platform AS platform, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS dp_count, ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
'AND session_history_media_info.audio_decision = "copy") ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS ds_count, ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'AND session_history_media_info.audio_decision = "transcode") ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tc_count, ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tc_count, ' \
'SUM(CASE WHEN session_history.stopped > 0 ' \ 'SUM(CASE WHEN session_history.stopped > 0 ' \
@ -838,12 +828,12 @@ class Graphs(object):
if y_axis == 'plays': if y_axis == 'plays':
query = 'SELECT ' \ query = 'SELECT ' \
'(CASE WHEN users.friendly_name IS NULL THEN users.username ELSE users.friendly_name END) AS username, ' \ '(CASE WHEN users.friendly_name IS NULL THEN users.username ELSE users.friendly_name END) AS username, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") THEN 1 ELSE 0 END) AS dp_count, ' \ 'THEN 1 ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
'AND session_history_media_info.audio_decision = "copy") THEN 1 ELSE 0 END) AS ds_count, ' \ 'THEN 1 ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'OR session_history_media_info.audio_decision = "transcode") THEN 1 ELSE 0 END) AS tc_count, ' \ 'THEN 1 ELSE 0 END) AS tc_count, ' \
'COUNT(session_history.id) AS total_count ' \ 'COUNT(session_history.id) AS total_count ' \
'FROM session_history ' \ 'FROM session_history ' \
'JOIN users ON session_history.user_id = users.user_id ' \ 'JOIN users ON session_history.user_id = users.user_id ' \
@ -858,16 +848,13 @@ class Graphs(object):
else: else:
query = 'SELECT ' \ query = 'SELECT ' \
'(CASE WHEN users.friendly_name IS NULL THEN users.username ELSE users.friendly_name END) AS username, ' \ '(CASE WHEN users.friendly_name IS NULL THEN users.username ELSE users.friendly_name END) AS username, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS dp_count, ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
'AND session_history_media_info.audio_decision = "copy") ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS ds_count, ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'AND session_history_media_info.audio_decision = "transcode") ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tc_count, ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tc_count, ' \
'SUM(CASE WHEN session_history.stopped > 0 ' \ 'SUM(CASE WHEN session_history.stopped > 0 ' \

View file

@ -148,7 +148,6 @@ class Libraries(object):
'session_history_metadata.year', 'session_history_metadata.year',
'session_history_metadata.media_index', 'session_history_metadata.media_index',
'session_history_metadata.parent_media_index', 'session_history_metadata.parent_media_index',
'session_history_media_info.video_decision',
'library_sections.do_notify', 'library_sections.do_notify',
'library_sections.do_notify_created', 'library_sections.do_notify_created',
'library_sections.keep_history' 'library_sections.keep_history'
@ -540,7 +539,7 @@ class Libraries(object):
def get_details(self, section_id=None): def get_details(self, section_id=None):
from plexpy import pmsconnect from plexpy import pmsconnect
default_return = {'section_id': None, default_return = {'section_id': 0,
'section_name': 'Local', 'section_name': 'Local',
'section_type': '', 'section_type': '',
'library_thumb': common.DEFAULT_COVER_THUMB, 'library_thumb': common.DEFAULT_COVER_THUMB,
@ -550,7 +549,7 @@ class Libraries(object):
'child_count': 0, 'child_count': 0,
'do_notify': 0, 'do_notify': 0,
'do_notify_created': 0, 'do_notify_created': 0,
'keep_history': 0 'keep_history': 1
} }
if not section_id: if not section_id:
@ -603,7 +602,8 @@ class Libraries(object):
return library_details return library_details
else: else:
logger.warn(u"PlexPy Libraries :: Unable to retrieve library from local database. Requesting library list refresh.") logger.warn(u"PlexPy Libraries :: Unable to retrieve library %s from database. Requesting library list refresh."
% section_id)
# Let's first refresh the libraries list to make sure the library isn't newly added and not in the db yet # Let's first refresh the libraries list to make sure the library isn't newly added and not in the db yet
pmsconnect.refresh_libraries() pmsconnect.refresh_libraries()
@ -613,9 +613,9 @@ class Libraries(object):
return library_details return library_details
else: else:
logger.warn(u"PlexPy Users :: Unable to retrieve user from local database. Returning 'Local' library.") logger.warn(u"PlexPy Users :: Unable to retrieve library %s from database. Returning 'Local' library."
% section_id)
# If there is no library data we must return something # If there is no library data we must return something
# Use "Local" library to retain compatibility with PlexWatch database value
return default_return return default_return
def get_watch_time_stats(self, section_id=None): def get_watch_time_stats(self, section_id=None):

View file

@ -50,7 +50,10 @@ def notify(stream_data=None, notify_action=None):
for agent in notifiers.available_notification_agents(): for agent in notifiers.available_notification_agents():
if agent['on_play'] and notify_action == 'play': if agent['on_play'] and notify_action == 'play':
# Build and send notification # Build and send notification
notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action) notify_strings, metadata = build_notify_text(session=stream_data,
notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'], notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0], subject=notify_strings[0],
body=notify_strings[1], body=notify_strings[1],
@ -68,7 +71,10 @@ def notify(stream_data=None, notify_action=None):
elif agent['on_stop'] and notify_action == 'stop' \ elif agent['on_stop'] and notify_action == 'stop' \
and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < plexpy.CONFIG.NOTIFY_WATCHED_PERCENT): and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < plexpy.CONFIG.NOTIFY_WATCHED_PERCENT):
# Build and send notification # Build and send notification
notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action) notify_strings, metadata = build_notify_text(session=stream_data,
notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'], notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0], subject=notify_strings[0],
body=notify_strings[1], body=notify_strings[1],
@ -86,7 +92,10 @@ def notify(stream_data=None, notify_action=None):
elif agent['on_pause'] and notify_action == 'pause' \ elif agent['on_pause'] and notify_action == 'pause' \
and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < 99): and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < 99):
# Build and send notification # Build and send notification
notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action) notify_strings, metadata = build_notify_text(session=stream_data,
notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'], notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0], subject=notify_strings[0],
body=notify_strings[1], body=notify_strings[1],
@ -104,7 +113,10 @@ def notify(stream_data=None, notify_action=None):
elif agent['on_resume'] and notify_action == 'resume' \ elif agent['on_resume'] and notify_action == 'resume' \
and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < 99): and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < 99):
# Build and send notification # Build and send notification
notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action) notify_strings, metadata = build_notify_text(session=stream_data,
notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'], notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0], subject=notify_strings[0],
body=notify_strings[1], body=notify_strings[1],
@ -121,7 +133,10 @@ def notify(stream_data=None, notify_action=None):
elif agent['on_buffer'] and notify_action == 'buffer': elif agent['on_buffer'] and notify_action == 'buffer':
# Build and send notification # Build and send notification
notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action) notify_strings, metadata = build_notify_text(session=stream_data,
notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'], notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0], subject=notify_strings[0],
body=notify_strings[1], body=notify_strings[1],
@ -143,7 +158,10 @@ def notify(stream_data=None, notify_action=None):
# If there is nothing in the notify_log for our agent id but it is enabled we should notify # If there is nothing in the notify_log for our agent id but it is enabled we should notify
if not any(d['agent_id'] == agent['id'] and d['notify_action'] == notify_action for d in notify_states): if not any(d['agent_id'] == agent['id'] and d['notify_action'] == notify_action for d in notify_states):
# Build and send notification # Build and send notification
notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action) notify_strings, metadata = build_notify_text(session=stream_data,
notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'], notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0], subject=notify_strings[0],
body=notify_strings[1], body=notify_strings[1],
@ -163,7 +181,10 @@ def notify(stream_data=None, notify_action=None):
for agent in notifiers.available_notification_agents(): for agent in notifiers.available_notification_agents():
if agent['on_play'] and notify_action == 'play': if agent['on_play'] and notify_action == 'play':
# Build and send notification # Build and send notification
notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action) notify_strings, metadata = build_notify_text(session=stream_data,
notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'], notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0], subject=notify_strings[0],
body=notify_strings[1], body=notify_strings[1],
@ -180,7 +201,10 @@ def notify(stream_data=None, notify_action=None):
elif agent['on_stop'] and notify_action == 'stop': elif agent['on_stop'] and notify_action == 'stop':
# Build and send notification # Build and send notification
notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action) notify_strings, metadata = build_notify_text(session=stream_data,
notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'], notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0], subject=notify_strings[0],
body=notify_strings[1], body=notify_strings[1],
@ -197,7 +221,10 @@ def notify(stream_data=None, notify_action=None):
elif agent['on_pause'] and notify_action == 'pause': elif agent['on_pause'] and notify_action == 'pause':
# Build and send notification # Build and send notification
notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action) notify_strings, metadata = build_notify_text(session=stream_data,
notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'], notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0], subject=notify_strings[0],
body=notify_strings[1], body=notify_strings[1],
@ -214,7 +241,10 @@ def notify(stream_data=None, notify_action=None):
elif agent['on_resume'] and notify_action == 'resume': elif agent['on_resume'] and notify_action == 'resume':
# Build and send notification # Build and send notification
notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action) notify_strings, metadata = build_notify_text(session=stream_data,
notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'], notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0], subject=notify_strings[0],
body=notify_strings[1], body=notify_strings[1],
@ -231,7 +261,10 @@ def notify(stream_data=None, notify_action=None):
elif agent['on_buffer'] and notify_action == 'buffer': elif agent['on_buffer'] and notify_action == 'buffer':
# Build and send notification # Build and send notification
notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action) notify_strings, metadata = build_notify_text(session=stream_data,
notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'], notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0], subject=notify_strings[0],
body=notify_strings[1], body=notify_strings[1],
@ -260,7 +293,10 @@ def notify_timeline(timeline_data=None, notify_action=None):
for agent in notifiers.available_notification_agents(): for agent in notifiers.available_notification_agents():
if agent['on_created'] and notify_action == 'created': if agent['on_created'] and notify_action == 'created':
# Build and send notification # Build and send notification
notify_strings, metadata = build_notify_text(timeline=timeline_data, notify_action=notify_action) notify_strings, metadata = build_notify_text(timeline=timeline_data,
notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'], notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0], subject=notify_strings[0],
body=notify_strings[1], body=notify_strings[1],
@ -279,7 +315,9 @@ def notify_timeline(timeline_data=None, notify_action=None):
for agent in notifiers.available_notification_agents(): for agent in notifiers.available_notification_agents():
if agent['on_extdown'] and notify_action == 'extdown': if agent['on_extdown'] and notify_action == 'extdown':
# Build and send notification # Build and send notification
notify_strings = build_server_notify_text(notify_action=notify_action) notify_strings = build_server_notify_text(notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'], notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0], subject=notify_strings[0],
body=notify_strings[1], body=notify_strings[1],
@ -295,7 +333,9 @@ def notify_timeline(timeline_data=None, notify_action=None):
if agent['on_intdown'] and notify_action == 'intdown': if agent['on_intdown'] and notify_action == 'intdown':
# Build and send notification # Build and send notification
notify_strings = build_server_notify_text(notify_action=notify_action) notify_strings = build_server_notify_text(notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'], notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0], subject=notify_strings[0],
body=notify_strings[1], body=notify_strings[1],
@ -311,7 +351,9 @@ def notify_timeline(timeline_data=None, notify_action=None):
if agent['on_extup'] and notify_action == 'extup': if agent['on_extup'] and notify_action == 'extup':
# Build and send notification # Build and send notification
notify_strings = build_server_notify_text(notify_action=notify_action) notify_strings = build_server_notify_text(notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'], notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0], subject=notify_strings[0],
body=notify_strings[1], body=notify_strings[1],
@ -327,7 +369,27 @@ def notify_timeline(timeline_data=None, notify_action=None):
if agent['on_intup'] and notify_action == 'intup': if agent['on_intup'] and notify_action == 'intup':
# Build and send notification # Build and send notification
notify_strings = build_server_notify_text(notify_action=notify_action) notify_strings = build_server_notify_text(notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1],
script_args=notify_strings[2],
notify_action=notify_action)
# Set the notification state in the db
set_notify_state(session={},
notify_action=notify_action,
agent_info=agent,
notify_strings=notify_strings,
metadata={})
if agent['on_pmsupdate'] and notify_action == 'pmsupdate':
# Build and send notification
notify_strings = build_server_notify_text(notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'], notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0], subject=notify_strings[0],
body=notify_strings[1], body=notify_strings[1],
@ -395,11 +457,11 @@ def set_notify_state(session, notify_action, agent_info, notify_strings, metadat
logger.error(u"PlexPy NotificationHandler :: Unable to set notify state.") logger.error(u"PlexPy NotificationHandler :: Unable to set notify state.")
def build_notify_text(session=None, timeline=None, notify_action=None): def build_notify_text(session=None, timeline=None, notify_action=None, agent_id=None):
# Get time formats # Get time formats
date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','').replace('zz','') date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','')
time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','') time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','')
duration_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','').replace('a','').replace('A','') duration_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('a','').replace('A','')
# Get the server name # Get the server name
server_name = plexpy.CONFIG.PMS_NAME server_name = plexpy.CONFIG.PMS_NAME
@ -408,6 +470,10 @@ def build_notify_text(session=None, timeline=None, notify_action=None):
plex_tv = plextv.PlexTV() plex_tv = plextv.PlexTV()
server_times = plex_tv.get_server_times() server_times = plex_tv.get_server_times()
# Get the server version
pms_connect = pmsconnect.PmsConnect()
server_identity = pms_connect.get_server_identity()
if server_times: if server_times:
updated_at = server_times[0]['updated_at'] updated_at = server_times[0]['updated_at']
server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_int(updated_at))) server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_int(updated_at)))
@ -435,13 +501,13 @@ def build_notify_text(session=None, timeline=None, notify_action=None):
# Check for exclusion tags # Check for exclusion tags
if metadata['media_type'] == 'movie': if metadata['media_type'] == 'movie':
# Regex pattern to remove the text in the tags we don't want # Regex pattern to remove the text in the tags we don't want
pattern = re.compile('\n*<tv>[^>]+.</tv>\n*|\n*<music>[^>]+.</music>\n*', re.IGNORECASE | re.DOTALL) pattern = re.compile(r'<tv>[^>]+.<\/tv>|<music>[^>]+.<\/music>', re.IGNORECASE | re.DOTALL)
elif metadata['media_type'] == 'show' or metadata['media_type'] == 'episode': elif metadata['media_type'] == 'show' or metadata['media_type'] == 'episode':
# Regex pattern to remove the text in the tags we don't want # Regex pattern to remove the text in the tags we don't want
pattern = re.compile('\n*<movie>[^>]+.</movie>\n*|\n*?<music>[^>]+.</music>\n*', re.IGNORECASE | re.DOTALL) pattern = re.compile(r'<movie>[^>]+.<\/movie>|<music>[^>]+.<\/music>', re.IGNORECASE | re.DOTALL)
elif metadata['media_type'] == 'artist' or metadata['media_type'] == 'track': elif metadata['media_type'] == 'artist' or metadata['media_type'] == 'track':
# Regex pattern to remove the text in the tags we don't want # Regex pattern to remove the text in the tags we don't want
pattern = re.compile('\n*<tv>[^>]+.</tv>\n*|\n*<movie>[^>]+.</movie>\n*', re.IGNORECASE | re.DOTALL) pattern = re.compile(r'<tv>[^>]+.<\/tv>|<movie>[^>]+.<\/movie>', re.IGNORECASE | re.DOTALL)
else: else:
pattern = None pattern = None
@ -450,37 +516,37 @@ def build_notify_text(session=None, timeline=None, notify_action=None):
or metadata['media_type'] == 'artist' or metadata['media_type'] == 'track' \ or metadata['media_type'] == 'artist' or metadata['media_type'] == 'track' \
and pattern: and pattern:
# Remove the unwanted tags and strip any unmatch tags too. # Remove the unwanted tags and strip any unmatch tags too.
on_start_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT)) on_start_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT), agent_id)
on_start_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT)) on_start_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT), agent_id)
on_stop_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT)) on_stop_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT), agent_id)
on_stop_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT)) on_stop_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT), agent_id)
on_pause_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT)) on_pause_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT), agent_id)
on_pause_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT)) on_pause_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT), agent_id)
on_resume_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT)) on_resume_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT), agent_id)
on_resume_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT)) on_resume_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT), agent_id)
on_buffer_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT)) on_buffer_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT), agent_id)
on_buffer_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT)) on_buffer_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT), agent_id)
on_watched_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT)) on_watched_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT), agent_id)
on_watched_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT)) on_watched_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT), agent_id)
on_created_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT)) on_created_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT), agent_id)
on_created_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT)) on_created_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT), agent_id)
script_args_text = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT)) script_args_text = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT), agent_id)
else: else:
on_start_subject = plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT on_start_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT, agent_id)
on_start_body = plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT on_start_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT, agent_id)
on_stop_subject = plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT on_stop_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT, agent_id)
on_stop_body = plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT on_stop_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT, agent_id)
on_pause_subject = plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT on_pause_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT, agent_id)
on_pause_body = plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT on_pause_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT, agent_id)
on_resume_subject = plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT on_resume_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT, agent_id)
on_resume_body = plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT on_resume_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT, agent_id)
on_buffer_subject = plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT on_buffer_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT, agent_id)
on_buffer_body = plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT on_buffer_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT, agent_id)
on_watched_subject = plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT on_watched_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT, agent_id)
on_watched_body = plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT on_watched_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT, agent_id)
on_created_subject = plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT on_created_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT, agent_id)
on_created_body = plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT on_created_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT, agent_id)
script_args_text = plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT script_args_text = strip_tag(plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT, agent_id)
# Create a title # Create a title
if metadata['media_type'] == 'episode' or metadata['media_type'] == 'track': if metadata['media_type'] == 'episode' or metadata['media_type'] == 'track':
@ -591,6 +657,7 @@ def build_notify_text(session=None, timeline=None, notify_action=None):
available_params = {# Global paramaters available_params = {# Global paramaters
'server_name': server_name, 'server_name': server_name,
'server_uptime': server_uptime, 'server_uptime': server_uptime,
'server_version': server_identity.get('version',''),
'action': notify_action.title(), 'action': notify_action.title(),
'datestamp': arrow.now().format(date_format), 'datestamp': arrow.now().format(date_format),
'timestamp': arrow.now().format(time_format), 'timestamp': arrow.now().format(time_format),
@ -854,10 +921,10 @@ def build_notify_text(session=None, timeline=None, notify_action=None):
return None return None
def build_server_notify_text(notify_action=None): def build_server_notify_text(notify_action=None, agent_id=None):
# Get time formats # Get time formats
date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','').replace('zz','') date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','')
time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','') time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','')
# Get the server name # Get the server name
server_name = plexpy.CONFIG.PMS_NAME server_name = plexpy.CONFIG.PMS_NAME
@ -866,6 +933,14 @@ def build_server_notify_text(notify_action=None):
plex_tv = plextv.PlexTV() plex_tv = plextv.PlexTV()
server_times = plex_tv.get_server_times() server_times = plex_tv.get_server_times()
# Get the server version
pms_connect = pmsconnect.PmsConnect()
server_identity = pms_connect.get_server_identity()
update_status = {}
if notify_action == 'pmsupdate':
update_status = pms_connect.get_update_staus()
if server_times: if server_times:
updated_at = server_times[0]['updated_at'] updated_at = server_times[0]['updated_at']
server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_int(updated_at))) server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_int(updated_at)))
@ -875,22 +950,29 @@ def build_server_notify_text(notify_action=None):
pattern = re.compile('\n*<tv>[^>]+.</tv>\n*|\n*<movie>[^>]+.</movie>\n*|\n*?<music>[^>]+.</music>\n*', re.IGNORECASE | re.DOTALL) pattern = re.compile('\n*<tv>[^>]+.</tv>\n*|\n*<movie>[^>]+.</movie>\n*|\n*?<music>[^>]+.</music>\n*', re.IGNORECASE | re.DOTALL)
on_extdown_subject = plexpy.CONFIG.NOTIFY_ON_EXTDOWN_SUBJECT_TEXT on_extdown_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_EXTDOWN_SUBJECT_TEXT, agent_id)
on_extdown_body = plexpy.CONFIG.NOTIFY_ON_EXTDOWN_BODY_TEXT on_extdown_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_EXTDOWN_BODY_TEXT, agent_id)
on_intdown_subject = plexpy.CONFIG.NOTIFY_ON_INTDOWN_SUBJECT_TEXT on_intdown_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_INTDOWN_SUBJECT_TEXT, agent_id)
on_intdown_body = plexpy.CONFIG.NOTIFY_ON_INTDOWN_BODY_TEXT on_intdown_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_INTDOWN_BODY_TEXT, agent_id)
on_extup_subject = plexpy.CONFIG.NOTIFY_ON_EXTUP_SUBJECT_TEXT on_extup_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_EXTUP_SUBJECT_TEXT, agent_id)
on_extup_body = plexpy.CONFIG.NOTIFY_ON_EXTUP_BODY_TEXT on_extup_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_EXTUP_BODY_TEXT, agent_id)
on_intup_subject = plexpy.CONFIG.NOTIFY_ON_INTUP_SUBJECT_TEXT on_intup_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_INTUP_SUBJECT_TEXT, agent_id)
on_intup_body = plexpy.CONFIG.NOTIFY_ON_INTUP_BODY_TEXT on_intup_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_INTUP_BODY_TEXT, agent_id)
script_args_text = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT)) on_pmsupdate_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_PMSUPDATE_SUBJECT_TEXT, agent_id)
on_pmsupdate_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_PMSUPDATE_BODY_TEXT, agent_id)
script_args_text = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT), agent_id)
available_params = {# Global paramaters available_params = {# Global paramaters
'server_name': server_name, 'server_name': server_name,
'server_uptime': server_uptime, 'server_uptime': server_uptime,
'server_version': server_identity.get('version',''),
'action': notify_action.title(), 'action': notify_action.title(),
'datestamp': arrow.now().format(date_format), 'datestamp': arrow.now().format(date_format),
'timestamp': arrow.now().format(time_format)} 'timestamp': arrow.now().format(time_format),
# Update parameters
'update_version': update_status.get('version',''),
'update_url': update_status.get('download_url',''),
'update_changelog': update_status.get('changelog','')}
# Default text # Default text
subject_text = 'PlexPy (%s)' % server_name subject_text = 'PlexPy (%s)' % server_name
@ -996,10 +1078,39 @@ def build_server_notify_text(notify_action=None):
else: else:
return [subject_text, body_text, script_args] return [subject_text, body_text, script_args]
elif notify_action == 'pmsupdate':
# Default body text
body_text = 'An update is available for the Plex Media Server (version {update_version}).'
if on_pmsupdate_subject and on_pmsupdate_body:
try:
subject_text = unicode(on_pmsupdate_subject).format(**available_params)
except LookupError, e:
logger.error(u"PlexPy NotificationHandler :: Unable to parse field %s in notification subject. Using fallback." % e)
except:
logger.error(u"PlexPy NotificationHandler :: Unable to parse custom notification subject. Using fallback.")
try:
body_text = unicode(on_pmsupdate_body).format(**available_params)
except LookupError, e:
logger.error(u"PlexPy NotificationHandler :: Unable to parse field %s in notification body. Using fallback." % e)
except:
logger.error(u"PlexPy NotificationHandler :: Unable to parse custom notification body. Using fallback.")
return [subject_text, body_text, script_args]
else:
return [subject_text, body_text, script_args]
else: else:
return None return None
def strip_tag(data): def strip_tag(data, agent_id=None):
p = re.compile(r'<.*?>') # Allow tags b, i, u, a[href], font[color] for Pushover
if agent_id == 7:
p = re.compile(r'<(?!/?(b>|i>|u>)|(a\shref=\"[^\"\'\s]+\"|/a>|font\scolor=\"[^\"\'\s]+\"|/font>)).*?>',
re.IGNORECASE | re.DOTALL)
else:
p = re.compile(r'<.*?>', re.IGNORECASE | re.DOTALL)
return p.sub('', data) return p.sub('', data)

View file

@ -75,7 +75,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.GROWL_ON_EXTDOWN, 'on_extdown': plexpy.CONFIG.GROWL_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.GROWL_ON_INTDOWN, 'on_intdown': plexpy.CONFIG.GROWL_ON_INTDOWN,
'on_extup': plexpy.CONFIG.GROWL_ON_EXTUP, 'on_extup': plexpy.CONFIG.GROWL_ON_EXTUP,
'on_intup': plexpy.CONFIG.GROWL_ON_INTUP 'on_intup': plexpy.CONFIG.GROWL_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.GROWL_ON_PMSUPDATE
}, },
{'name': 'Prowl', {'name': 'Prowl',
'id': AGENT_IDS['Prowl'], 'id': AGENT_IDS['Prowl'],
@ -92,7 +93,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.PROWL_ON_EXTDOWN, 'on_extdown': plexpy.CONFIG.PROWL_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.PROWL_ON_INTDOWN, 'on_intdown': plexpy.CONFIG.PROWL_ON_INTDOWN,
'on_extup': plexpy.CONFIG.PROWL_ON_EXTUP, 'on_extup': plexpy.CONFIG.PROWL_ON_EXTUP,
'on_intup': plexpy.CONFIG.PROWL_ON_INTUP 'on_intup': plexpy.CONFIG.PROWL_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.PROWL_ON_PMSUPDATE
}, },
{'name': 'XBMC', {'name': 'XBMC',
'id': AGENT_IDS['XBMC'], 'id': AGENT_IDS['XBMC'],
@ -109,7 +111,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.XBMC_ON_EXTDOWN, 'on_extdown': plexpy.CONFIG.XBMC_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.XBMC_ON_INTDOWN, 'on_intdown': plexpy.CONFIG.XBMC_ON_INTDOWN,
'on_extup': plexpy.CONFIG.XBMC_ON_EXTUP, 'on_extup': plexpy.CONFIG.XBMC_ON_EXTUP,
'on_intup': plexpy.CONFIG.XBMC_ON_INTUP 'on_intup': plexpy.CONFIG.XBMC_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.XBMC_ON_PMSUPDATE
}, },
{'name': 'Plex', {'name': 'Plex',
'id': AGENT_IDS['Plex'], 'id': AGENT_IDS['Plex'],
@ -126,7 +129,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.PLEX_ON_EXTDOWN, 'on_extdown': plexpy.CONFIG.PLEX_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.PLEX_ON_INTDOWN, 'on_intdown': plexpy.CONFIG.PLEX_ON_INTDOWN,
'on_extup': plexpy.CONFIG.PLEX_ON_EXTUP, 'on_extup': plexpy.CONFIG.PLEX_ON_EXTUP,
'on_intup': plexpy.CONFIG.PLEX_ON_INTUP 'on_intup': plexpy.CONFIG.PLEX_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.PLEX_ON_PMSUPDATE
}, },
{'name': 'NotifyMyAndroid', {'name': 'NotifyMyAndroid',
'id': AGENT_IDS['NMA'], 'id': AGENT_IDS['NMA'],
@ -143,7 +147,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.NMA_ON_EXTDOWN, 'on_extdown': plexpy.CONFIG.NMA_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.NMA_ON_INTDOWN, 'on_intdown': plexpy.CONFIG.NMA_ON_INTDOWN,
'on_extup': plexpy.CONFIG.NMA_ON_EXTUP, 'on_extup': plexpy.CONFIG.NMA_ON_EXTUP,
'on_intup': plexpy.CONFIG.NMA_ON_INTUP 'on_intup': plexpy.CONFIG.NMA_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.NMA_ON_PMSUPDATE
}, },
{'name': 'Pushalot', {'name': 'Pushalot',
'id': AGENT_IDS['Pushalot'], 'id': AGENT_IDS['Pushalot'],
@ -160,7 +165,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.PUSHALOT_ON_EXTDOWN, 'on_extdown': plexpy.CONFIG.PUSHALOT_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.PUSHALOT_ON_INTDOWN, 'on_intdown': plexpy.CONFIG.PUSHALOT_ON_INTDOWN,
'on_extup': plexpy.CONFIG.PUSHALOT_ON_EXTUP, 'on_extup': plexpy.CONFIG.PUSHALOT_ON_EXTUP,
'on_intup': plexpy.CONFIG.PUSHALOT_ON_INTUP 'on_intup': plexpy.CONFIG.PUSHALOT_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.PUSHALOT_ON_PMSUPDATE
}, },
{'name': 'Pushbullet', {'name': 'Pushbullet',
'id': AGENT_IDS['Pushbullet'], 'id': AGENT_IDS['Pushbullet'],
@ -177,7 +183,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.PUSHBULLET_ON_EXTDOWN, 'on_extdown': plexpy.CONFIG.PUSHBULLET_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.PUSHBULLET_ON_INTDOWN, 'on_intdown': plexpy.CONFIG.PUSHBULLET_ON_INTDOWN,
'on_extup': plexpy.CONFIG.PUSHBULLET_ON_EXTUP, 'on_extup': plexpy.CONFIG.PUSHBULLET_ON_EXTUP,
'on_intup': plexpy.CONFIG.PUSHBULLET_ON_INTUP 'on_intup': plexpy.CONFIG.PUSHBULLET_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.PUSHBULLET_ON_PMSUPDATE
}, },
{'name': 'Pushover', {'name': 'Pushover',
'id': AGENT_IDS['Pushover'], 'id': AGENT_IDS['Pushover'],
@ -194,7 +201,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.PUSHOVER_ON_EXTDOWN, 'on_extdown': plexpy.CONFIG.PUSHOVER_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.PUSHOVER_ON_INTDOWN, 'on_intdown': plexpy.CONFIG.PUSHOVER_ON_INTDOWN,
'on_extup': plexpy.CONFIG.PUSHOVER_ON_EXTUP, 'on_extup': plexpy.CONFIG.PUSHOVER_ON_EXTUP,
'on_intup': plexpy.CONFIG.PUSHOVER_ON_INTUP 'on_intup': plexpy.CONFIG.PUSHOVER_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.PUSHOVER_ON_PMSUPDATE
}, },
{'name': 'Boxcar2', {'name': 'Boxcar2',
'id': AGENT_IDS['Boxcar2'], 'id': AGENT_IDS['Boxcar2'],
@ -211,7 +219,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.BOXCAR_ON_EXTDOWN, 'on_extdown': plexpy.CONFIG.BOXCAR_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.BOXCAR_ON_INTDOWN, 'on_intdown': plexpy.CONFIG.BOXCAR_ON_INTDOWN,
'on_extup': plexpy.CONFIG.BOXCAR_ON_EXTUP, 'on_extup': plexpy.CONFIG.BOXCAR_ON_EXTUP,
'on_intup': plexpy.CONFIG.BOXCAR_ON_INTUP 'on_intup': plexpy.CONFIG.BOXCAR_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.BOXCAR_ON_PMSUPDATE
}, },
{'name': 'E-mail', {'name': 'E-mail',
'id': AGENT_IDS['Email'], 'id': AGENT_IDS['Email'],
@ -228,7 +237,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.EMAIL_ON_EXTDOWN, 'on_extdown': plexpy.CONFIG.EMAIL_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.EMAIL_ON_INTDOWN, 'on_intdown': plexpy.CONFIG.EMAIL_ON_INTDOWN,
'on_extup': plexpy.CONFIG.EMAIL_ON_EXTUP, 'on_extup': plexpy.CONFIG.EMAIL_ON_EXTUP,
'on_intup': plexpy.CONFIG.EMAIL_ON_INTUP 'on_intup': plexpy.CONFIG.EMAIL_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.EMAIL_ON_PMSUPDATE
}, },
{'name': 'Twitter', {'name': 'Twitter',
'id': AGENT_IDS['Twitter'], 'id': AGENT_IDS['Twitter'],
@ -245,7 +255,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.TWITTER_ON_EXTDOWN, 'on_extdown': plexpy.CONFIG.TWITTER_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.TWITTER_ON_INTDOWN, 'on_intdown': plexpy.CONFIG.TWITTER_ON_INTDOWN,
'on_extup': plexpy.CONFIG.TWITTER_ON_EXTUP, 'on_extup': plexpy.CONFIG.TWITTER_ON_EXTUP,
'on_intup': plexpy.CONFIG.TWITTER_ON_INTUP 'on_intup': plexpy.CONFIG.TWITTER_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.TWITTER_ON_PMSUPDATE
}, },
{'name': 'IFTTT', {'name': 'IFTTT',
'id': AGENT_IDS['IFTTT'], 'id': AGENT_IDS['IFTTT'],
@ -262,7 +273,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.IFTTT_ON_EXTDOWN, 'on_extdown': plexpy.CONFIG.IFTTT_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.IFTTT_ON_INTDOWN, 'on_intdown': plexpy.CONFIG.IFTTT_ON_INTDOWN,
'on_extup': plexpy.CONFIG.IFTTT_ON_EXTUP, 'on_extup': plexpy.CONFIG.IFTTT_ON_EXTUP,
'on_intup': plexpy.CONFIG.IFTTT_ON_INTUP 'on_intup': plexpy.CONFIG.IFTTT_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.IFTTT_ON_PMSUPDATE
}, },
{'name': 'Telegram', {'name': 'Telegram',
'id': AGENT_IDS['Telegram'], 'id': AGENT_IDS['Telegram'],
@ -279,7 +291,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.TELEGRAM_ON_EXTDOWN, 'on_extdown': plexpy.CONFIG.TELEGRAM_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.TELEGRAM_ON_INTDOWN, 'on_intdown': plexpy.CONFIG.TELEGRAM_ON_INTDOWN,
'on_extup': plexpy.CONFIG.TELEGRAM_ON_EXTUP, 'on_extup': plexpy.CONFIG.TELEGRAM_ON_EXTUP,
'on_intup': plexpy.CONFIG.TELEGRAM_ON_INTUP 'on_intup': plexpy.CONFIG.TELEGRAM_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.TELEGRAM_ON_PMSUPDATE
}, },
{'name': 'Slack', {'name': 'Slack',
'id': AGENT_IDS['Slack'], 'id': AGENT_IDS['Slack'],
@ -296,7 +309,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.SLACK_ON_EXTDOWN, 'on_extdown': plexpy.CONFIG.SLACK_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.SLACK_ON_INTDOWN, 'on_intdown': plexpy.CONFIG.SLACK_ON_INTDOWN,
'on_extup': plexpy.CONFIG.SLACK_ON_EXTUP, 'on_extup': plexpy.CONFIG.SLACK_ON_EXTUP,
'on_intup': plexpy.CONFIG.SLACK_ON_INTUP 'on_intup': plexpy.CONFIG.SLACK_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.SLACK_ON_PMSUPDATE
}, },
{'name': 'Scripts', {'name': 'Scripts',
'id': AGENT_IDS['Scripts'], 'id': AGENT_IDS['Scripts'],
@ -313,7 +327,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.SCRIPTS_ON_EXTDOWN, 'on_extdown': plexpy.CONFIG.SCRIPTS_ON_EXTDOWN,
'on_extup': plexpy.CONFIG.SCRIPTS_ON_EXTUP, 'on_extup': plexpy.CONFIG.SCRIPTS_ON_EXTUP,
'on_intdown': plexpy.CONFIG.SCRIPTS_ON_INTDOWN, 'on_intdown': plexpy.CONFIG.SCRIPTS_ON_INTDOWN,
'on_intup': plexpy.CONFIG.SCRIPTS_ON_INTUP 'on_intup': plexpy.CONFIG.SCRIPTS_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.SCRIPTS_ON_PMSUPDATE
}, },
{'name': 'Facebook', {'name': 'Facebook',
'id': AGENT_IDS['Facebook'], 'id': AGENT_IDS['Facebook'],
@ -330,7 +345,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.FACEBOOK_ON_EXTDOWN, 'on_extdown': plexpy.CONFIG.FACEBOOK_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.FACEBOOK_ON_INTDOWN, 'on_intdown': plexpy.CONFIG.FACEBOOK_ON_INTDOWN,
'on_extup': plexpy.CONFIG.FACEBOOK_ON_EXTUP, 'on_extup': plexpy.CONFIG.FACEBOOK_ON_EXTUP,
'on_intup': plexpy.CONFIG.FACEBOOK_ON_INTUP 'on_intup': plexpy.CONFIG.FACEBOOK_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.FACEBOOK_ON_PMSUPDATE
} }
] ]
@ -352,7 +368,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.OSX_NOTIFY_ON_EXTDOWN, 'on_extdown': plexpy.CONFIG.OSX_NOTIFY_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.OSX_NOTIFY_ON_INTDOWN, 'on_intdown': plexpy.CONFIG.OSX_NOTIFY_ON_INTDOWN,
'on_extup': plexpy.CONFIG.OSX_NOTIFY_ON_EXTUP, 'on_extup': plexpy.CONFIG.OSX_NOTIFY_ON_EXTUP,
'on_intup': plexpy.CONFIG.OSX_NOTIFY_ON_INTUP 'on_intup': plexpy.CONFIG.OSX_NOTIFY_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.OSX_NOTIFY_ON_PMSUPDATE
}) })
return agents return agents
@ -1266,7 +1283,7 @@ class TwitterNotifier(object):
def return_config_options(self): def return_config_options(self):
config_option = [{'label': 'Instructions', config_option = [{'label': 'Instructions',
'description': 'Step 1: Visit <a href="https://apps.twitter.com/" target="_blank"> \ 'description': 'Step 1: Visit <a href="' + helpers.anon_url('https://apps.twitter.com') + '" target="_blank"> \
Twitter Apps</a> to <strong>Create New App</strong>. A vaild "Website" is not required.<br>\ Twitter Apps</a> to <strong>Create New App</strong>. A vaild "Website" is not required.<br>\
Step 2: Go to <strong>Keys and Access Tokens</strong> and click \ Step 2: Go to <strong>Keys and Access Tokens</strong> and click \
<strong>Create my access token</strong>.<br>\ <strong>Create my access token</strong>.<br>\
@ -1301,7 +1318,7 @@ class TwitterNotifier(object):
{'label': 'Include Subject Line', {'label': 'Include Subject Line',
'value': self.incl_subject, 'value': self.incl_subject,
'name': 'twitter_incl_subject', 'name': 'twitter_incl_subject',
'description': 'Include the subject line in the notifications.', 'description': 'Include the subject line with the notifications.',
'input_type': 'checkbox' 'input_type': 'checkbox'
} }
] ]
@ -1625,7 +1642,8 @@ class IFTTT(object):
config_option = [{'label': 'Ifttt Maker Channel Key', config_option = [{'label': 'Ifttt Maker Channel Key',
'value': self.apikey, 'value': self.apikey,
'name': 'ifttt_key', 'name': 'ifttt_key',
'description': 'Your Ifttt key. You can get a key from <a href="https://ifttt.com/maker" target="_blank">here</a>.', 'description': 'Your Ifttt key. You can get a key from'
' <a href="' + helpers.anon_url('https://ifttt.com/maker') + '" target="_blank">here</a>.',
'input_type': 'text' 'input_type': 'text'
}, },
{'label': 'Ifttt Event', {'label': 'Ifttt Event',
@ -1699,19 +1717,23 @@ class TELEGRAM(object):
config_option = [{'label': 'Telegram Bot Token', config_option = [{'label': 'Telegram Bot Token',
'value': self.bot_token, 'value': self.bot_token,
'name': 'telegram_bot_token', 'name': 'telegram_bot_token',
'description': 'Your Telegram bot token. Contact <a href="http://telegram.me/BotFather" target="_blank">@BotFather</a> on Telegram to get one.', 'description': 'Your Telegram bot token. '
'Contact <a href="' + helpers.anon_url('https://telegram.me/BotFather') + '" target="_blank">@BotFather</a>'
' on Telegram to get one.',
'input_type': 'text' 'input_type': 'text'
}, },
{'label': 'Telegram Chat ID, Group ID, or Channel Username', {'label': 'Telegram Chat ID, Group ID, or Channel Username',
'value': self.chat_id, 'value': self.chat_id,
'name': 'telegram_chat_id', 'name': 'telegram_chat_id',
'description': 'Your Telegram Chat ID, Group ID, or @channelusername. Contact <a href="http://telegram.me/myidbot" target="_blank">@myidbot</a> on Telegram to get an ID.', 'description': 'Your Telegram Chat ID, Group ID, or @channelusername. '
'Contact <a href="' + helpers.anon_url('https://telegram.me/myidbot') + '" target="_blank">@myidbot</a>'
' on Telegram to get an ID.',
'input_type': 'text' 'input_type': 'text'
}, },
{'label': 'Include Subject Line', {'label': 'Include Subject Line',
'value': self.incl_subject, 'value': self.incl_subject,
'name': 'telegram_incl_subject', 'name': 'telegram_incl_subject',
'description': 'Include the subject line in the notifications.', 'description': 'Include the subject line with the notifications.',
'input_type': 'checkbox' 'input_type': 'checkbox'
} }
] ]
@ -1809,7 +1831,7 @@ class SLACK(object):
{'label': 'Include Subject Line', {'label': 'Include Subject Line',
'value': self.incl_subject, 'value': self.incl_subject,
'name': 'slack_incl_subject', 'name': 'slack_incl_subject',
'description': 'Include the subject line in the notifications.', 'description': 'Include the subject line with the notifications.',
'input_type': 'checkbox' 'input_type': 'checkbox'
} }
] ]
@ -1880,14 +1902,14 @@ class Scripts(object):
elif notify_action == 'resume': elif notify_action == 'resume':
script = plexpy.CONFIG.SCRIPTS_ON_RESUME_SCRIPT script = plexpy.CONFIG.SCRIPTS_ON_RESUME_SCRIPT
elif notify_action == 'watched':
script = plexpy.CONFIG.SCRIPTS_ON_WATCHED_SCRIPT
elif notify_action == 'buffer': elif notify_action == 'buffer':
script = plexpy.CONFIG.SCRIPTS_ON_BUFFER_SCRIPT script = plexpy.CONFIG.SCRIPTS_ON_BUFFER_SCRIPT
elif notify_action == 'extdown': elif notify_action == 'created':
script = plexpy.CONFIG.SCRIPTS_ON_EXTDOWN_SCRIPT script = plexpy.CONFIG.SCRIPTS_ON_CREATED_SCRIPT
elif notify_action == 'extup':
script = plexpy.CONFIG.SCRIPTS_ON_EXTUP_SCRIPT
elif notify_action == 'intdown': elif notify_action == 'intdown':
script = plexpy.CONFIG.SCRIPTS_ON_INTDOWN_SCRIPT script = plexpy.CONFIG.SCRIPTS_ON_INTDOWN_SCRIPT
@ -1895,11 +1917,14 @@ class Scripts(object):
elif notify_action == 'intup': elif notify_action == 'intup':
script = plexpy.CONFIG.SCRIPTS_ON_INTUP_SCRIPT script = plexpy.CONFIG.SCRIPTS_ON_INTUP_SCRIPT
elif notify_action == 'created': elif notify_action == 'extdown':
script = plexpy.CONFIG.SCRIPTS_ON_CREATED_SCRIPT script = plexpy.CONFIG.SCRIPTS_ON_EXTDOWN_SCRIPT
elif notify_action == 'watched': elif notify_action == 'extup':
script = plexpy.CONFIG.SCRIPTS_ON_WATCHED_SCRIPT script = plexpy.CONFIG.SCRIPTS_ON_EXTUP_SCRIPT
elif notify_action == 'pmsupdate':
script = plexpy.CONFIG.SCRIPTS_ON_PMSUPDATE_SCRIPT
else: else:
# For manual scripts # For manual scripts
@ -2041,13 +2066,6 @@ class Scripts(object):
'input_type': 'select', 'input_type': 'select',
'select_options': self.list_scripts() 'select_options': self.list_scripts()
}, },
{'label': 'Plex Remote Access Down',
'value': plexpy.CONFIG.SCRIPTS_ON_EXTDOWN_SCRIPT,
'name': 'scripts_on_extdown_script',
'description': 'Choose the script for Plex remote access down.',
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'Plex Server Down', {'label': 'Plex Server Down',
'value': plexpy.CONFIG.SCRIPTS_ON_INTDOWN_SCRIPT, 'value': plexpy.CONFIG.SCRIPTS_ON_INTDOWN_SCRIPT,
'name': 'scripts_on_intdown_script', 'name': 'scripts_on_intdown_script',
@ -2055,6 +2073,20 @@ class Scripts(object):
'input_type': 'select', 'input_type': 'select',
'select_options': self.list_scripts() 'select_options': self.list_scripts()
}, },
{'label': 'Plex Server Back Up',
'value': plexpy.CONFIG.SCRIPTS_ON_INTUP_SCRIPT,
'name': 'scripts_on_intup_script',
'description': 'Choose the script for Plex server back up.',
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'Plex Remote Access Down',
'value': plexpy.CONFIG.SCRIPTS_ON_EXTDOWN_SCRIPT,
'name': 'scripts_on_extdown_script',
'description': 'Choose the script for Plex remote access down.',
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'Plex Remote Access Back Up', {'label': 'Plex Remote Access Back Up',
'value': plexpy.CONFIG.SCRIPTS_ON_EXTUP_SCRIPT, 'value': plexpy.CONFIG.SCRIPTS_ON_EXTUP_SCRIPT,
'name': 'scripts_on_extup_script', 'name': 'scripts_on_extup_script',
@ -2062,10 +2094,10 @@ class Scripts(object):
'input_type': 'select', 'input_type': 'select',
'select_options': self.list_scripts() 'select_options': self.list_scripts()
}, },
{'label': 'Plex Server Back Up', {'label': 'Plex Update Available',
'value': plexpy.CONFIG.SCRIPTS_ON_INTUP_SCRIPT, 'value': plexpy.CONFIG.SCRIPTS_ON_PMSUPDATE_SCRIPT,
'name': 'scripts_on_intup_script', 'name': 'scripts_on_pmsupdate_script',
'description': 'Choose the script for Plex server back up.', 'description': 'Choose the script for Plex update available.',
'input_type': 'select', 'input_type': 'select',
'select_options': self.list_scripts() 'select_options': self.list_scripts()
} }
@ -2082,6 +2114,7 @@ class FacebookNotifier(object):
self.app_id = plexpy.CONFIG.FACEBOOK_APP_ID self.app_id = plexpy.CONFIG.FACEBOOK_APP_ID
self.app_secret = plexpy.CONFIG.FACEBOOK_APP_SECRET self.app_secret = plexpy.CONFIG.FACEBOOK_APP_SECRET
self.group_id = plexpy.CONFIG.FACEBOOK_GROUP self.group_id = plexpy.CONFIG.FACEBOOK_GROUP
self.incl_pmslink = plexpy.CONFIG.FACEBOOK_INCL_PMSLINK
self.incl_poster = plexpy.CONFIG.FACEBOOK_INCL_POSTER self.incl_poster = plexpy.CONFIG.FACEBOOK_INCL_POSTER
self.incl_subject = plexpy.CONFIG.FACEBOOK_INCL_SUBJECT self.incl_subject = plexpy.CONFIG.FACEBOOK_INCL_SUBJECT
@ -2139,10 +2172,27 @@ class FacebookNotifier(object):
poster_url = metadata.get('poster_url','') poster_url = metadata.get('poster_url','')
if poster_url: if poster_url:
if metadata['media_type'] == 'movie' or metadata['media_type'] == 'show': if metadata['media_type'] == 'movie':
title = metadata['title'] title = metadata['title']
subtitle = metadata['year'] subtitle = metadata['year']
rating_key = metadata['rating_key'] rating_key = metadata['rating_key']
if metadata.get('imdb_url',''):
poster_link = metadata.get('imdb_url', '')
caption = 'View on IMDB.'
elif metadata.get('themoviedb_url',''):
poster_link = metadata.get('themoviedb_url', '')
caption = 'View on The Movie Database.'
elif metadata['media_type'] == 'show':
title = metadata['title']
subtitle = metadata['year']
rating_key = metadata['rating_key']
if metadata.get('thetvdb_url',''):
poster_link = metadata.get('thetvdb_url', '')
caption = 'View on TheTVDB.'
elif metadata.get('themoviedb_url',''):
poster_link = metadata.get('themoviedb_url', '')
caption = 'View on The Movie Database.'
elif metadata['media_type'] == 'episode': elif metadata['media_type'] == 'episode':
title = '%s - %s' % (metadata['grandparent_title'], metadata['title']) title = '%s - %s' % (metadata['grandparent_title'], metadata['title'])
@ -2150,26 +2200,44 @@ class FacebookNotifier(object):
'\xc2\xb7'.decode('utf8'), '\xc2\xb7'.decode('utf8'),
metadata['media_index']) metadata['media_index'])
rating_key = metadata['rating_key'] rating_key = metadata['rating_key']
if metadata.get('thetvdb_url',''):
poster_link = metadata.get('thetvdb_url', '')
caption = 'View on TheTVDB.'
elif metadata.get('themoviedb_url',''):
poster_link = metadata.get('themoviedb_url', '')
caption = 'View on The Movie Database.'
elif metadata['media_type'] == 'artist': elif metadata['media_type'] == 'artist':
title = metadata['title'] title = metadata['title']
subtitle = '' subtitle = ''
rating_key = metadata['rating_key'] rating_key = metadata['rating_key']
if metadata.get('lastfm_url',''):
poster_link = metadata.get('lastfm_url', '')
caption = 'View on Last.fm.'
elif metadata['media_type'] == 'track': elif metadata['media_type'] == 'track':
title = '%s - %s' % (metadata['grandparent_title'], metadata['title']) title = '%s - %s' % (metadata['grandparent_title'], metadata['title'])
subtitle = metadata['parent_title'] subtitle = metadata['parent_title']
rating_key = metadata['parent_rating_key'] rating_key = metadata['parent_rating_key']
if metadata.get('lastfm_url',''):
caption = 'View in Plex Web.' poster_link = metadata.get('lastfm_url', '')
caption = 'View on Last.fm.'
# Build Facebook post attachment # Build Facebook post attachment
attachment['link'] = 'http://app.plex.tv/web/app#!/server/' + plexpy.CONFIG.PMS_IDENTIFIER + \ if self.incl_pmslink:
'/details/%2Flibrary%2Fmetadata%2F' + rating_key caption = 'View on Plex Web.'
attachment['link'] = 'http://app.plex.tv/web/app#!/server/' + plexpy.CONFIG.PMS_IDENTIFIER + \
'/details/%2Flibrary%2Fmetadata%2F' + rating_key
attachment['caption'] = caption
elif poster_link:
attachment['link'] = poster_link
attachment['caption'] = caption
else:
attachment['link'] = poster_url
attachment['picture'] = poster_url attachment['picture'] = poster_url
attachment['name'] = title attachment['name'] = title
attachment['description'] = subtitle attachment['description'] = subtitle
attachment['caption'] = caption
try: try:
api.put_wall_post(profile_id=self.group_id, message=message, attachment=attachment) api.put_wall_post(profile_id=self.group_id, message=message, attachment=attachment)
@ -2186,7 +2254,7 @@ class FacebookNotifier(object):
def return_config_options(self): def return_config_options(self):
config_option = [{'label': 'Instructions', config_option = [{'label': 'Instructions',
'description': '<strong>Facebook notifications are currently experimental!</strong><br><br> \ 'description': '<strong>Facebook notifications are currently experimental!</strong><br><br> \
Step 1: Visit <a href="https://developers.facebook.com/apps/" target="_blank"> \ Step 1: Visit <a href="' + helpers.anon_url('https://developers.facebook.com/apps') + '" target="_blank"> \
Facebook Developers</a> to add a new app using <strong>basic setup</strong>.<br>\ Facebook Developers</a> to add a new app using <strong>basic setup</strong>.<br>\
Step 2: Go to <strong>Settings > Basic</strong> and fill in a \ Step 2: Go to <strong>Settings > Basic</strong> and fill in a \
<strong>Contact Email</strong>.<br>\ <strong>Contact Email</strong>.<br>\
@ -2195,7 +2263,7 @@ class FacebookNotifier(object):
Step 4: Go to <strong>App Review</strong> and toggle public to <strong>Yes</strong>.<br>\ Step 4: Go to <strong>App Review</strong> and toggle public to <strong>Yes</strong>.<br>\
Step 5: Fill in the <strong>PlexPy URL</strong> below with the exact same URL from Step 3.<br>\ Step 5: Fill in the <strong>PlexPy URL</strong> below with the exact same URL from Step 3.<br>\
Step 6: Fill in the <strong>App ID</strong> and <strong>App Secret</strong> below.<br>\ Step 6: Fill in the <strong>App ID</strong> and <strong>App Secret</strong> below.<br>\
Step 7: Click the <strong>Request Authorization</strong> button below.<br> \ Step 7: Click the <strong>Request Authorization</strong> button below.<br>\
Step 8: Fill in the <strong>Group ID</strong> below.', Step 8: Fill in the <strong>Group ID</strong> below.',
'input_type': 'help' 'input_type': 'help'
}, },
@ -2232,13 +2300,20 @@ class FacebookNotifier(object):
{'label': 'Include Poster Image', {'label': 'Include Poster Image',
'value': self.incl_poster, 'value': self.incl_poster,
'name': 'facebook_incl_poster', 'name': 'facebook_incl_poster',
'description': 'Include a poster and link in the notifications.', 'description': 'Include a poster with the notifications.',
'input_type': 'checkbox'
},
{'label': 'Include Link to Plex Web',
'value': self.incl_pmslink,
'name': 'facebook_incl_pmslink',
'description': 'Include a link to the media in Plex Web with the notifications.<br>'
'If disabled, the link will go to IMDB, TVDB, TMDb, or Last.fm instead, if available.',
'input_type': 'checkbox' 'input_type': 'checkbox'
}, },
{'label': 'Include Subject Line', {'label': 'Include Subject Line',
'value': self.incl_subject, 'value': self.incl_subject,
'name': 'facebook_incl_subject', 'name': 'facebook_incl_subject',
'description': 'Include the subject line in the notifications.', 'description': 'Include the subject line with the notifications.',
'input_type': 'checkbox' 'input_type': 'checkbox'
} }
] ]

View file

@ -251,7 +251,7 @@ def import_from_plexwatch(database=None, table_name=None, import_ignore_interval
hours=0, minutes=0, seconds=0) hours=0, minutes=0, seconds=0)
plexpy.schedule_job(activity_pinger.check_recently_added, 'Check for recently added items', plexpy.schedule_job(activity_pinger.check_recently_added, 'Check for recently added items',
hours=0, minutes=0, seconds=0) hours=0, minutes=0, seconds=0)
plexpy.schedule_job(activity_pinger.check_server_response, 'Check for server response', plexpy.schedule_job(activity_pinger.check_server_response, 'Check for Plex remote access',
hours=0, minutes=0, seconds=0) hours=0, minutes=0, seconds=0)
ap = activity_processor.ActivityProcessor() ap = activity_processor.ActivityProcessor()
@ -302,7 +302,7 @@ def import_from_plexwatch(database=None, table_name=None, import_ignore_interval
# Skip line if we don't have a ratingKey to work with # Skip line if we don't have a ratingKey to work with
if not row['rating_key']: if not row['rating_key']:
logger.error(u"PlexPy Importer :: Skipping record due to null ratingRey.") logger.error(u"PlexPy Importer :: Skipping record due to null ratingKey.")
continue continue
# If the user_id no longer exists in the friends list, pull it from the xml. # If the user_id no longer exists in the friends list, pull it from the xml.

View file

@ -389,6 +389,38 @@ class PmsConnect(object):
return request return request
def put_updater(self, output_format=''):
"""
Refresh updater status.
Optional parameters: output_format { dict, json }
Output: array
"""
uri = '/updater/check?download=0'
request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='PUT',
output_format=output_format)
return request
def get_updater(self, output_format=''):
"""
Return updater status.
Optional parameters: output_format { dict, json }
Output: array
"""
uri = '/updater/status'
request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET',
output_format=output_format)
return request
def get_recently_added_details(self, section_id='', count='0'): def get_recently_added_details(self, section_id='', count='0'):
""" """
Return processed and validated list of recently added items. Return processed and validated list of recently added items.
@ -1008,7 +1040,7 @@ class PmsConnect(object):
'user_id': user_details['user_id'], 'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'], 'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['user_thumb'], 'user_thumb': user_details['user_thumb'],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-1], 'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split('::ffff:')[-1],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'), 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id, 'machine_id': machine_id,
@ -1130,7 +1162,7 @@ class PmsConnect(object):
'user_id': user_details['user_id'], 'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'], 'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['user_thumb'], 'user_thumb': user_details['user_thumb'],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-1], 'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split('::ffff:')[-1],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'), 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id, 'machine_id': machine_id,
@ -1188,7 +1220,7 @@ class PmsConnect(object):
'user_id': user_details['user_id'], 'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'], 'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['user_thumb'], 'user_thumb': user_details['user_thumb'],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-1], 'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split('::ffff:')[-1],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'), 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id, 'machine_id': machine_id,
@ -1246,7 +1278,7 @@ class PmsConnect(object):
'user_id': user_details['user_id'], 'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'], 'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['user_thumb'], 'user_thumb': user_details['user_thumb'],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-1], 'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split('::ffff:')[-1],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'), 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id, 'machine_id': machine_id,
@ -1337,7 +1369,7 @@ class PmsConnect(object):
'user_id': user_details['user_id'], 'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'], 'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['user_thumb'], 'user_thumb': user_details['user_thumb'],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-1], 'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split('::ffff:')[-1],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'), 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id, 'machine_id': machine_id,
@ -1479,7 +1511,7 @@ class PmsConnect(object):
xml_head = identity.getElementsByTagName('MediaContainer') xml_head = identity.getElementsByTagName('MediaContainer')
except Exception as e: except Exception as e:
logger.warn(u"PlexPy Pmsconnect :: Unable to parse XML for get_local_server_identity: %s." % e) logger.warn(u"PlexPy Pmsconnect :: Unable to parse XML for get_local_server_identity: %s." % e)
return [] return {}
server_identity = {} server_identity = {}
for a in xml_head: for a in xml_head:
@ -1995,3 +2027,40 @@ class PmsConnect(object):
} }
return server_response return server_response
def get_update_staus(self):
# Refresh the Plex updater status first
self.put_updater()
updater_status = self.get_updater(output_format='xml')
try:
xml_head = updater_status.getElementsByTagName('MediaContainer')
except Exception as e:
logger.warn(u"PlexPy Pmsconnect :: Unable to parse XML for get_update_staus: %s." % e)
# Catch the malformed XML on certain PMX version.
# XML parser helper returns empty list if there is an error parsing XML
if updater_status == []:
logger.warn(u"Plex API updater XML is broken on the current PMS version. Please update your PMS manually.")
logger.info(u"PlexPy is unable to check for Plex updates. Disabling check for Plex updates.")
# Disable check for Plex updates
plexpy.CONFIG.MONITOR_PMS_UPDATES = 0
plexpy.initialize_scheduler()
plexpy.CONFIG.write()
return {}
updater_info = {}
for a in xml_head:
if a.getElementsByTagName('Release'):
release = a.getElementsByTagName('Release')
for item in release:
updater_info = {'can_install': helpers.get_xml_attr(a, 'canInstall'),
'download_url': helpers.get_xml_attr(a, 'downloadURL'),
'version': helpers.get_xml_attr(item, 'version'),
'state': helpers.get_xml_attr(item, 'state'),
'changelog': helpers.get_xml_attr(item, 'fixed')
}
return updater_info

View file

@ -50,8 +50,7 @@ class Users(object):
'session_history_metadata.year', 'session_history_metadata.year',
'session_history_metadata.media_index', 'session_history_metadata.media_index',
'session_history_metadata.parent_media_index', 'session_history_metadata.parent_media_index',
'session_history_media_info.video_decision', 'session_history_media_info.transcode_decision',
'session_history_media_info.audio_decision',
'users.do_notify as do_notify', 'users.do_notify as do_notify',
'users.keep_history as keep_history' 'users.keep_history as keep_history'
] ]
@ -117,8 +116,7 @@ class Users(object):
'year': item['year'], 'year': item['year'],
'media_index': item['media_index'], 'media_index': item['media_index'],
'parent_media_index': item['parent_media_index'], 'parent_media_index': item['parent_media_index'],
'video_decision': item['video_decision'], 'transcode_decision': item['transcode_decision'],
'audio_decision': item['audio_decision'],
'do_notify': helpers.checked(item['do_notify']), 'do_notify': helpers.checked(item['do_notify']),
'keep_history': helpers.checked(item['keep_history']) 'keep_history': helpers.checked(item['keep_history'])
} }
@ -154,8 +152,7 @@ class Users(object):
'session_history_metadata.year', 'session_history_metadata.year',
'session_history_metadata.media_index', 'session_history_metadata.media_index',
'session_history_metadata.parent_media_index', 'session_history_metadata.parent_media_index',
'session_history_media_info.video_decision', 'session_history_media_info.transcode_decision',
'session_history_media_info.audio_decision',
'session_history.user', 'session_history.user',
'session_history.user_id as custom_user_id', 'session_history.user_id as custom_user_id',
'(CASE WHEN users.friendly_name IS NULL THEN users.username ELSE \ '(CASE WHEN users.friendly_name IS NULL THEN users.username ELSE \
@ -213,8 +210,7 @@ class Users(object):
'year': item['year'], 'year': item['year'],
'media_index': item['media_index'], 'media_index': item['media_index'],
'parent_media_index': item['parent_media_index'], 'parent_media_index': item['parent_media_index'],
'video_decision': item['video_decision'], 'transcode_decision': item['transcode_decision'],
'audio_decision': item['audio_decision'],
'friendly_name': item['friendly_name'] 'friendly_name': item['friendly_name']
} }
@ -245,7 +241,7 @@ class Users(object):
def get_details(self, user_id=None, user=None): def get_details(self, user_id=None, user=None):
from plexpy import plextv from plexpy import plextv
default_return = {'user_id': None, default_return = {'user_id': 0,
'username': 'Local', 'username': 'Local',
'friendly_name': 'Local', 'friendly_name': 'Local',
'user_thumb': common.DEFAULT_USER_THUMB, 'user_thumb': common.DEFAULT_USER_THUMB,
@ -254,7 +250,7 @@ class Users(object):
'is_allow_sync': 0, 'is_allow_sync': 0,
'is_restricted': 0, 'is_restricted': 0,
'do_notify': 0, 'do_notify': 0,
'keep_history': 0 'keep_history': 1
} }
if not user_id and not user: if not user_id and not user:
@ -316,7 +312,8 @@ class Users(object):
return user_details return user_details
else: else:
logger.warn(u"PlexPy Users :: Unable to retrieve user from local database. Requesting user list refresh.") logger.warn(u"PlexPy Users :: Unable to retrieve user %s from database. Requesting user list refresh."
% user_id if user_id else user)
# Let's first refresh the user list to make sure the user isn't newly added and not in the db yet # Let's first refresh the user list to make sure the user isn't newly added and not in the db yet
plextv.refresh_users() plextv.refresh_users()
@ -326,7 +323,8 @@ class Users(object):
return user_details return user_details
else: else:
logger.warn(u"PlexPy Users :: Unable to retrieve user from local database. Returning 'Local' user.") logger.warn(u"PlexPy Users :: Unable to retrieve user %s from database. Returning 'Local' user."
% user_id if user_id else user)
# If there is no user data we must return something # If there is no user data we must return something
# Use "Local" user to retain compatibility with PlexWatch database value # Use "Local" user to retain compatibility with PlexWatch database value
return default_return return default_return

View file

@ -1,2 +1,2 @@
PLEXPY_VERSION = "master" PLEXPY_VERSION = "master"
PLEXPY_RELEASE_VERSION = "1.3.9" PLEXPY_RELEASE_VERSION = "1.3.10"

View file

@ -25,6 +25,7 @@ import websocket
name = 'websocket' name = 'websocket'
opcode_data = (websocket.ABNF.OPCODE_TEXT, websocket.ABNF.OPCODE_BINARY) opcode_data = (websocket.ABNF.OPCODE_TEXT, websocket.ABNF.OPCODE_BINARY)
ws_reconnect = False
def start_thread(): def start_thread():
@ -34,6 +35,11 @@ def start_thread():
threading.Thread(target=run).start() threading.Thread(target=run).start()
def reconnect():
global ws_reconnect
ws_reconnect = True
def run(): def run():
from websocket import create_connection from websocket import create_connection
@ -51,19 +57,21 @@ def run():
if plexpy.CONFIG.PMS_TOKEN: if plexpy.CONFIG.PMS_TOKEN:
uri += '?X-Plex-Token=' + plexpy.CONFIG.PMS_TOKEN uri += '?X-Plex-Token=' + plexpy.CONFIG.PMS_TOKEN
global ws_reconnect
ws_reconnect = False
ws_connected = False ws_connected = False
reconnects = 0 reconnects = 0
# Try an open the websocket connection - if it fails after 15 retries fallback to polling # Try an open the websocket connection - if it fails after 15 retries fallback to polling
while not ws_connected and reconnects <= 15: while not ws_connected and reconnects <= 15:
try: try:
logger.info(u'PlexPy WebSocket :: Opening%s websocket, connection attempt %s.' % (secure, str(reconnects + 1))) logger.info(u"PlexPy WebSocket :: Opening%s websocket, connection attempt %s." % (secure, str(reconnects + 1)))
ws = create_connection(uri) ws = create_connection(uri)
reconnects = 0 reconnects = 0
ws_connected = True ws_connected = True
logger.info(u'PlexPy WebSocket :: Ready') logger.info(u"PlexPy WebSocket :: Ready")
except IOError, e: except IOError, e:
logger.error(u'PlexPy WebSocket :: %s.' % e) logger.error(u"PlexPy WebSocket :: %s." % e)
reconnects += 1 reconnects += 1
time.sleep(5) time.sleep(5)
@ -81,22 +89,30 @@ def run():
if reconnects > 1: if reconnects > 1:
time.sleep(5) time.sleep(5)
logger.warn(u'PlexPy WebSocket :: Connection has closed, reconnecting...') logger.warn(u"PlexPy WebSocket :: Connection has closed, reconnecting...")
try: try:
ws = create_connection(uri) ws = create_connection(uri)
except IOError, e: except IOError, e:
logger.info(u'PlexPy WebSocket :: %s.' % e) logger.info(u"PlexPy WebSocket :: %s." % e)
else: else:
ws.shutdown()
ws_connected = False ws_connected = False
break break
if not ws_connected: # Check if we recieved a restart notification and close websocket connection cleanly
logger.error(u'PlexPy WebSocket :: Connection unavailable, falling back to polling.') if ws_reconnect:
logger.info(u"PlexPy WebSocket :: Reconnecting websocket...")
ws.shutdown()
ws_connected = False
start_thread()
if not ws_connected and not ws_reconnect:
logger.error(u"PlexPy WebSocket :: Connection unavailable, falling back to polling.")
plexpy.POLLING_FAILOVER = True plexpy.POLLING_FAILOVER = True
plexpy.initialize_scheduler() plexpy.initialize_scheduler()
logger.debug(u'PlexPy WebSocket :: Leaving thread.') logger.debug(u"PlexPy WebSocket :: Leaving thread.")
def receive(ws): def receive(ws):
@ -124,7 +140,7 @@ def process(opcode, data):
try: try:
info = json.loads(data) info = json.loads(data)
except Exception as ex: except Exception as ex:
logger.warn(u'PlexPy WebSocket :: Error decoding message from websocket: %s' % ex) logger.warn(u"PlexPy WebSocket :: Error decoding message from websocket: %s" % ex)
logger.debug(data) logger.debug(data)
return False return False

View file

@ -13,7 +13,8 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>. # along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
from plexpy import logger, notifiers, plextv, pmsconnect, common, log_reader, datafactory, graphs, users, libraries, database from plexpy import logger, notifiers, plextv, pmsconnect, common, log_reader, \
datafactory, graphs, users, libraries, database, web_socket
from plexpy.helpers import checked, addtoapi, get_ip, create_https_certificates from plexpy.helpers import checked, addtoapi, get_ip, create_https_certificates
from mako.lookup import TemplateLookup from mako.lookup import TemplateLookup
@ -253,6 +254,18 @@ class WebInterface(object):
logger.warn(u"Unable to retrieve data for get_recently_added.") logger.warn(u"Unable to retrieve data for get_recently_added.")
return serve_template(templatename="recently_added.html", data=None) return serve_template(templatename="recently_added.html", data=None)
@cherrypy.expose
def delete_temp_sessions(self):
result = database.delete_sessions()
if result:
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps({'message': result})
else:
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps({'message': 'no data received'})
##### Libraries ##### ##### Libraries #####
@ -776,6 +789,10 @@ class WebInterface(object):
media_type = kwargs.get('media_type', "") media_type = kwargs.get('media_type', "")
if media_type != 'all': if media_type != 'all':
custom_where.append(['session_history.media_type', media_type]) custom_where.append(['session_history.media_type', media_type])
if 'transcode_decision' in kwargs:
transcode_decision = kwargs.get('transcode_decision', "")
if transcode_decision:
custom_where.append(['session_history_media_info.transcode_decision', transcode_decision])
data_factory = datafactory.DataFactory() data_factory = datafactory.DataFactory()
history = data_factory.get_datatables_history(kwargs=kwargs, custom_where=custom_where, grouping=grouping, watched_percent=watched_percent) history = data_factory.get_datatables_history(kwargs=kwargs, custom_where=custom_where, grouping=grouping, watched_percent=watched_percent)
@ -989,9 +1006,9 @@ class WebInterface(object):
logger.warn(u"Unable to retrieve data for get_stream_type_by_top_10_platforms.") logger.warn(u"Unable to retrieve data for get_stream_type_by_top_10_platforms.")
@cherrypy.expose @cherrypy.expose
def history_table_modal(self, start_date=None, **kwargs): def history_table_modal(self, **kwargs):
return serve_template(templatename="history_table_modal.html", title="History Data", data=start_date) return serve_template(templatename="history_table_modal.html", title="History Data", data=kwargs)
##### Sync ##### ##### Sync #####
@ -1091,6 +1108,19 @@ class WebInterface(object):
cherrypy.response.headers['Content-type'] = 'application/json' cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps(notifications) return json.dumps(notifications)
@cherrypy.expose
@addtoapi()
def clearNotifyLogs(self, **kwargs):
data_factory = datafactory.DataFactory()
result = data_factory.delete_notification_log()
if result:
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps({'message': result})
else:
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps({'message': 'no data received'})
@cherrypy.expose @cherrypy.expose
def clearLogs(self): def clearLogs(self):
plexpy.LOG_LIST = [] plexpy.LOG_LIST = []
@ -1147,8 +1177,9 @@ class WebInterface(object):
"api_key": plexpy.CONFIG.API_KEY, "api_key": plexpy.CONFIG.API_KEY,
"update_db_interval": plexpy.CONFIG.UPDATE_DB_INTERVAL, "update_db_interval": plexpy.CONFIG.UPDATE_DB_INTERVAL,
"freeze_db": checked(plexpy.CONFIG.FREEZE_DB), "freeze_db": checked(plexpy.CONFIG.FREEZE_DB),
"log_dir": plexpy.CONFIG.LOG_DIR, "backup_dir": plexpy.CONFIG.BACKUP_DIR,
"cache_dir": plexpy.CONFIG.CACHE_DIR, "cache_dir": plexpy.CONFIG.CACHE_DIR,
"log_dir": plexpy.CONFIG.LOG_DIR,
"check_github": checked(plexpy.CONFIG.CHECK_GITHUB), "check_github": checked(plexpy.CONFIG.CHECK_GITHUB),
"interface_list": interface_list, "interface_list": interface_list,
"cache_sizemb": plexpy.CONFIG.CACHE_SIZEMB, "cache_sizemb": plexpy.CONFIG.CACHE_SIZEMB,
@ -1178,6 +1209,7 @@ class WebInterface(object):
"tv_notify_on_pause": checked(plexpy.CONFIG.TV_NOTIFY_ON_PAUSE), "tv_notify_on_pause": checked(plexpy.CONFIG.TV_NOTIFY_ON_PAUSE),
"movie_notify_on_pause": checked(plexpy.CONFIG.MOVIE_NOTIFY_ON_PAUSE), "movie_notify_on_pause": checked(plexpy.CONFIG.MOVIE_NOTIFY_ON_PAUSE),
"music_notify_on_pause": checked(plexpy.CONFIG.MUSIC_NOTIFY_ON_PAUSE), "music_notify_on_pause": checked(plexpy.CONFIG.MUSIC_NOTIFY_ON_PAUSE),
"monitor_pms_updates": checked(plexpy.CONFIG.MONITOR_PMS_UPDATES),
"monitor_remote_access": checked(plexpy.CONFIG.MONITOR_REMOTE_ACCESS), "monitor_remote_access": checked(plexpy.CONFIG.MONITOR_REMOTE_ACCESS),
"monitoring_interval": plexpy.CONFIG.MONITORING_INTERVAL, "monitoring_interval": plexpy.CONFIG.MONITORING_INTERVAL,
"monitoring_use_websocket": checked(plexpy.CONFIG.MONITORING_USE_WEBSOCKET), "monitoring_use_websocket": checked(plexpy.CONFIG.MONITORING_USE_WEBSOCKET),
@ -1219,6 +1251,8 @@ class WebInterface(object):
"notify_on_extup_body_text": plexpy.CONFIG.NOTIFY_ON_EXTUP_BODY_TEXT, "notify_on_extup_body_text": plexpy.CONFIG.NOTIFY_ON_EXTUP_BODY_TEXT,
"notify_on_intup_subject_text": plexpy.CONFIG.NOTIFY_ON_INTUP_SUBJECT_TEXT, "notify_on_intup_subject_text": plexpy.CONFIG.NOTIFY_ON_INTUP_SUBJECT_TEXT,
"notify_on_intup_body_text": plexpy.CONFIG.NOTIFY_ON_INTUP_BODY_TEXT, "notify_on_intup_body_text": plexpy.CONFIG.NOTIFY_ON_INTUP_BODY_TEXT,
"notify_on_pmsupdate_subject_text": plexpy.CONFIG.NOTIFY_ON_PMSUPDATE_SUBJECT_TEXT,
"notify_on_pmsupdate_body_text": plexpy.CONFIG.NOTIFY_ON_PMSUPDATE_BODY_TEXT,
"notify_scripts_args_text": plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT, "notify_scripts_args_text": plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT,
"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),
@ -1246,12 +1280,15 @@ class WebInterface(object):
"refresh_libraries_on_startup", "refresh_users_on_startup", "refresh_libraries_on_startup", "refresh_users_on_startup",
"ip_logging_enable", "movie_logging_enable", "tv_logging_enable", "music_logging_enable", "ip_logging_enable", "movie_logging_enable", "tv_logging_enable", "music_logging_enable",
"pms_is_remote", "home_stats_type", "group_history_tables", "notify_consecutive", "notify_upload_posters", "pms_is_remote", "home_stats_type", "group_history_tables", "notify_consecutive", "notify_upload_posters",
"notify_recently_added", "notify_recently_added_grandparent", "monitor_remote_access", "get_file_sizes" "notify_recently_added", "notify_recently_added_grandparent",
"monitor_pms_updates", "monitor_remote_access", "get_file_sizes"
] ]
for checked_config in checked_configs: for checked_config in checked_configs:
if checked_config not in kwargs: if checked_config not in kwargs:
# checked items should be zero or one. if they were not sent then the item was not checked # checked items should be zero or one. if they were not sent then the item was not checked
kwargs[checked_config] = '0' kwargs[checked_config] = 0
else:
kwargs[checked_config] = 1
# If http password exists in config, do not overwrite when blank value received # If http password exists in config, do not overwrite when blank value received
if kwargs.get('http_password'): if kwargs.get('http_password'):
@ -1274,13 +1311,14 @@ class WebInterface(object):
if kwargs.get('monitoring_interval') != str(plexpy.CONFIG.MONITORING_INTERVAL) or \ if kwargs.get('monitoring_interval') != str(plexpy.CONFIG.MONITORING_INTERVAL) or \
kwargs.get('refresh_libraries_interval') != str(plexpy.CONFIG.REFRESH_LIBRARIES_INTERVAL) or \ kwargs.get('refresh_libraries_interval') != str(plexpy.CONFIG.REFRESH_LIBRARIES_INTERVAL) or \
kwargs.get('refresh_users_interval') != str(plexpy.CONFIG.REFRESH_USERS_INTERVAL) or \ kwargs.get('refresh_users_interval') != str(plexpy.CONFIG.REFRESH_USERS_INTERVAL) or \
kwargs.get('notify_recently_added') != str(plexpy.CONFIG.NOTIFY_RECENTLY_ADDED) or \ kwargs.get('notify_recently_added') != plexpy.CONFIG.NOTIFY_RECENTLY_ADDED or \
kwargs.get('monitor_remote_access') != str(plexpy.CONFIG.MONITOR_REMOTE_ACCESS): kwargs.get('monitor_pms_updates') != plexpy.CONFIG.MONITOR_PMS_UPDATES or \
kwargs.get('monitor_remote_access') != plexpy.CONFIG.MONITOR_REMOTE_ACCESS:
reschedule = True reschedule = True
# If we change the SSL setting for PMS or PMS remote setting, make sure we grab the new url. # If we change the SSL setting for PMS or PMS remote setting, make sure we grab the new url.
if kwargs.get('pms_ssl') != str(plexpy.CONFIG.PMS_SSL) or \ if kwargs.get('pms_ssl') != plexpy.CONFIG.PMS_SSL or \
kwargs.get('pms_is_remote') != str(plexpy.CONFIG.PMS_IS_REMOTE): kwargs.get('pms_is_remote') != plexpy.CONFIG.PMS_IS_REMOTE:
server_changed = True server_changed = True
# If we change the HTTPS setting, make sure we generate a new certificate. # If we change the HTTPS setting, make sure we generate a new certificate.
@ -1327,6 +1365,7 @@ class WebInterface(object):
if server_changed: if server_changed:
plextv.get_real_pms_url() plextv.get_real_pms_url()
pmsconnect.get_server_friendly_name() pmsconnect.get_server_friendly_name()
web_socket.reconnect()
# Reconfigure scheduler if intervals changed # Reconfigure scheduler if intervals changed
if reschedule: if reschedule:
@ -1630,11 +1669,16 @@ class WebInterface(object):
if source == 'history': if source == 'history':
data_factory = datafactory.DataFactory() data_factory = datafactory.DataFactory()
metadata = data_factory.get_metadata_details(rating_key=rating_key) metadata = data_factory.get_metadata_details(rating_key=rating_key)
poster_url = data_factory.get_poster_url(metadata=metadata)
metadata['poster_url'] = poster_url
else: else:
pms_connect = pmsconnect.PmsConnect() pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_metadata_details(rating_key=rating_key, get_media_info=True) result = pms_connect.get_metadata_details(rating_key=rating_key, get_media_info=True)
if result: if result:
metadata = result['metadata'] metadata = result['metadata']
data_factory = datafactory.DataFactory()
poster_url = data_factory.get_poster_url(metadata=metadata)
metadata['poster_url'] = poster_url
if metadata: if metadata:
return serve_template(templatename="info.html", data=metadata, title="Info", config=config, source=source) return serve_template(templatename="info.html", data=metadata, title="Info", config=config, source=source)
@ -1681,6 +1725,22 @@ class WebInterface(object):
return None return None
@cherrypy.expose
def delete_poster_url(self, poster_url=''):
if poster_url:
data_factory = datafactory.DataFactory()
result = data_factory.delete_poster_url(poster_url=poster_url)
else:
result = None
if result:
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps({'message': result})
else:
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps({'message': 'no data received'})
##### Search ##### ##### Search #####
@ -2183,3 +2243,9 @@ class WebInterface(object):
a = Api() a = Api()
a.checkParams(*args, **kwargs) a.checkParams(*args, **kwargs)
return a.fetchData() return a.fetchData()
@cherrypy.expose
def check_pms_updater(self):
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_update_staus()
return json.dumps(result)