mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-16 02:02:58 -07:00
Merge branch 'dev'
This commit is contained in:
commit
08537c1d69
50 changed files with 1904 additions and 784 deletions
98
CHANGELOG.md
98
CHANGELOG.md
|
@ -1,9 +1,39 @@
|
|||
# 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)
|
||||
|
||||
* 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)
|
||||
|
@ -12,8 +42,8 @@
|
|||
* Fix: Remove media tags from script arguments for server notifications.
|
||||
* Fix: Encode poster titles to UTF-8 for Imgur upload.
|
||||
* Fix: Allow notifications to send without poster if Imgur upload fails.
|
||||
* Add: Notification Logs table in the Logs tab.
|
||||
* Add: Toggle in settings to enable posters in notifications. (Disabled by default.)
|
||||
* New: Notification Logs table in the Logs tab.
|
||||
* 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: 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.
|
||||
|
@ -26,18 +56,18 @@
|
|||
* Fix: Video metadata flags showing up for track info.
|
||||
* Fix: Custom library icons not applied to Library Statistics.
|
||||
* Fix: Typos in the Web UI.
|
||||
* Add: ETA to Current Activity overlay.
|
||||
* Add: Total duration to Libraries and Users tables.
|
||||
* Add: {machine_id} to notification options.
|
||||
* Add: IMDB, TVDB, TMDb, Last.fm, and Trackt IDs/URLs to notification options.
|
||||
* Add: {poster_url} to notification options using Imgur.
|
||||
* Add: Poster and link for Facebook notifications.
|
||||
* Add: Log javascript errors from the Web UI.
|
||||
* Add: Configuration and Scheduler info to the settings page.
|
||||
* Add: Schedule background task to backup the PlexPy database.
|
||||
* Add: URL anonymizer for external links.
|
||||
* Add: Plex Media Scanner log file to Log viewer.
|
||||
* Add: API v2 (sill very experimental). (Thanks @Hellowlol)
|
||||
* New: ETA to Current Activity overlay.
|
||||
* New: Total duration to Libraries and Users tables.
|
||||
* New: {machine_id} to notification options.
|
||||
* New: IMDB, TVDB, TMDb, Last.fm, and Trackt IDs/URLs to notification options.
|
||||
* New: {poster_url} to notification options using Imgur.
|
||||
* New: Poster and link for Facebook notifications.
|
||||
* New: Log javascript errors from the Web UI.
|
||||
* New: Configuration and Scheduler info to the settings page.
|
||||
* New: Schedule background task to backup the PlexPy database.
|
||||
* New: URL anonymizer for external links.
|
||||
* New: Plex Media Scanner log file to Log viewer.
|
||||
* New: API v2 (sill very experimental). (Thanks @Hellowlol)
|
||||
* Change: Allow secure websocket connections.
|
||||
* Change: History grouping now accounts for the view offset.
|
||||
* 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: Server verification for unpublished servers.
|
||||
* Fix: Updating PMS identifier for server change.
|
||||
* Add: {stream_time}, {remaining_time}, and {progress_time} to notification options.
|
||||
* Add: Powershell script support. (Thanks @Hellowlol)
|
||||
* Add: Method to delete duplicate libraries.
|
||||
* New: {stream_time}, {remaining_time}, and {progress_time} to notification options.
|
||||
* New: Powershell script support. (Thanks @Hellowlol)
|
||||
* New: Method to delete duplicate libraries.
|
||||
* Change: Daemonize before running start up tasks.
|
||||
|
||||
|
||||
|
@ -74,7 +104,7 @@
|
|||
* Fix: Libraries and Users lists not refreshing.
|
||||
* Fix: Server verification in settings.
|
||||
* 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: Built in Twitter key and secret.
|
||||
* Change: Unnecessary quoting of script arguments.
|
||||
|
@ -107,21 +137,21 @@
|
|||
|
||||
## v1.3.0 (2016-01-23)
|
||||
|
||||
* Add: Brand new Libraries section.
|
||||
* Add: Lots of new library statistics.
|
||||
* Add: Media info table for libraries.
|
||||
* Add: Web app for Android and iOS. (Thanks @zobe123)
|
||||
* Add: Slack notification agent. (Thanks @richipargo)
|
||||
* Add: Facebook notification agent.
|
||||
* Add: Custom script notification agent. (Thanks @Hellowlol)
|
||||
* Add: Custom "From Name" to email notification agent.
|
||||
* Add: Ability to test notifications / send custom one-off notifications.
|
||||
* Add: 'datestamp' and 'timestamp' notification options.
|
||||
* Add: More concurrent stream statistics.
|
||||
* Add: Media info flags on the info pages.
|
||||
* Add: Ability to fix broken metadata if the item has been moved in Plex.
|
||||
* Add: Ability to rearrange the homepage statistics cards.
|
||||
* Add: CentOS startup script (Thanks @PHoSawyer)
|
||||
* New: Brand new Libraries section.
|
||||
* New: Lots of new library statistics.
|
||||
* New: Media info table for libraries.
|
||||
* New: Web app for Android and iOS. (Thanks @zobe123)
|
||||
* New: Slack notification agent. (Thanks @richipargo)
|
||||
* New: Facebook notification agent.
|
||||
* New: Custom script notification agent. (Thanks @Hellowlol)
|
||||
* New: Custom "From Name" to email notification agent.
|
||||
* New: Ability to test notifications / send custom one-off notifications.
|
||||
* New: 'datestamp' and 'timestamp' notification options.
|
||||
* New: More concurrent stream statistics.
|
||||
* New: Media info flags on the info pages.
|
||||
* New: Ability to fix broken metadata if the item has been moved in Plex.
|
||||
* New: Ability to rearrange the homepage statistics cards.
|
||||
* New: CentOS startup script (Thanks @PHoSawyer)
|
||||
* Fix: Server name blank after first run wizard.
|
||||
* Fix: Incorrect duration for grouped home stats.
|
||||
* Fix: Allow SSL when verifying server in settings.
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<%
|
||||
import plexpy
|
||||
from plexpy import version
|
||||
from plexpy.helpers import anon_url
|
||||
%>
|
||||
<!doctype html>
|
||||
|
||||
|
@ -136,15 +137,15 @@ from plexpy import version
|
|||
<div id="ajaxMsg" class="ajaxMsg"></div>
|
||||
% if plexpy.CONFIG.CHECK_GITHUB and not plexpy.CURRENT_VERSION:
|
||||
<div id="updatebar" style="display: none;">
|
||||
You're running an unknown version of PlexPy. <a href="update">Update</a> or
|
||||
<a href="#" id="updateDismiss">Close</a>
|
||||
You're running an unknown version of PlexPy.<br />
|
||||
<a href="update">Update</a> or <a href="#" id="updateDismiss">Close</a>
|
||||
</div>
|
||||
% 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;">
|
||||
A <a
|
||||
href="https://github.com/${plexpy.CONFIG.GIT_USER}/plexpy/compare/${plexpy.CURRENT_VERSION}...${plexpy.LATEST_VERSION}" target="_blank">
|
||||
newer version</a> is available. You're ${plexpy.COMMITS_BEHIND} commits behind. <a href="update">Update</a> or
|
||||
<a href="#" id="updateDismiss">Close</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">
|
||||
newer version</a> is available.<br />
|
||||
You're ${plexpy.COMMITS_BEHIND} commits behind.<br />
|
||||
<a href="update">Update</a> or <a href="#" id="updateDismiss">Close</a>
|
||||
</div>
|
||||
% endif
|
||||
<nav class="navbar navbar-fixed-top">
|
||||
|
|
|
@ -882,10 +882,11 @@ a .dashboard-activity-metadata-user-thumb:hover {
|
|||
}
|
||||
.dashboard-recent-media-row {
|
||||
width: 100%;
|
||||
margin:0 auto;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.dashboard-recent-media {
|
||||
width: 100%;
|
||||
|
@ -2407,6 +2408,9 @@ a .home-platforms-instance-list-oval:hover,
|
|||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
.history-thumbnail-popover.popover.left {
|
||||
margin-left: -15px;
|
||||
}
|
||||
.history-thumbnail-popover.popover.right {
|
||||
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;
|
||||
}
|
||||
.notification-params {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
background-color: #282828;
|
||||
}
|
||||
|
@ -2669,6 +2674,14 @@ table[id^='media_info_child'] table[id^='media_info_child'] thead th {
|
|||
padding-left: 10px;
|
||||
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) {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
@ -2758,4 +2771,22 @@ a.no-highlight:hover {
|
|||
}
|
||||
.save-button {
|
||||
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;
|
||||
}
|
|
@ -217,8 +217,8 @@ DOCUMENTATION :: END
|
|||
% endif
|
||||
<div class="dashboard-activity-progress">
|
||||
<div class="dashboard-activity-progress-bar">
|
||||
<div class="bufferbar" style="width: ${a['transcode_progress']}%">${a['transcode_progress']}%</div>
|
||||
<div class="bar" style="width: ${a['progress_percent']}%">${a['progress_percent']}%</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']}%" data-toggle="tooltip" title="Stream Progress">${a['progress_percent']}%</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-activity-metadata-wrapper">
|
||||
|
@ -261,11 +261,7 @@ DOCUMENTATION :: END
|
|||
% endif
|
||||
</div>
|
||||
<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>
|
||||
% else:
|
||||
<a href="user?user=${a['user']}" title="${a['friendly_name']}">${a['friendly_name']}</a>
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -289,11 +285,13 @@ DOCUMENTATION :: END
|
|||
});
|
||||
|
||||
// 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');
|
||||
}, function() {
|
||||
$(this).closest('.dashboard-instance').removeClass('hover');
|
||||
});
|
||||
|
||||
$('.bar, .bufferbar').tooltip({container: 'body', placement: 'right', delay: 50});
|
||||
</script>
|
||||
% else:
|
||||
<div class="text-muted">Nothing is currently being watched.</div><br>
|
||||
|
|
|
@ -249,7 +249,7 @@
|
|||
|
||||
<script>
|
||||
// Modal popup dialog
|
||||
function selectHandler(selectedDate) {
|
||||
function selectHandler(selectedDate, selectedSeries) {
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -259,10 +259,25 @@
|
|||
var y = dateValue.getFullYear();
|
||||
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({
|
||||
"url": "history_table_modal",
|
||||
url: "history_table_modal",
|
||||
type: 'post',
|
||||
data: { 'start_date': dateString },
|
||||
data: {
|
||||
start_date: dateString,
|
||||
media_type: media_type,
|
||||
transcode_decision: transcode_decision
|
||||
},
|
||||
complete: function(xhr, status) {
|
||||
$('#history-modal').modal('show');
|
||||
$("#history-modal").html(xhr.responseText);
|
||||
|
@ -271,7 +286,7 @@
|
|||
}
|
||||
catch(err)
|
||||
{
|
||||
console.log("Failed to retrieve data");
|
||||
console.log("Failed to retrieve history modal data.");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -299,6 +314,8 @@
|
|||
var music_visible = (${config['music_logging_enable']} == 1 ? true : false);
|
||||
|
||||
function loadGraphsTab1(time_range, yaxis) {
|
||||
$('#days-selection').show();
|
||||
|
||||
setGraphFormat(yaxis);
|
||||
|
||||
$.ajax({
|
||||
|
@ -382,6 +399,8 @@
|
|||
}
|
||||
|
||||
function loadGraphsTab2(time_range, yaxis) {
|
||||
$('#days-selection').show();
|
||||
|
||||
setGraphFormat(yaxis);
|
||||
|
||||
$.ajax({
|
||||
|
@ -460,6 +479,8 @@
|
|||
}
|
||||
|
||||
function loadGraphsTab3(yaxis) {
|
||||
$('#days-selection').hide();
|
||||
|
||||
setGraphFormat(yaxis);
|
||||
|
||||
$.ajax({
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class='table-card-back'>
|
||||
<table class="display" id="history_table" width="100%">
|
||||
<table class="display history_table" id="history_table" width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="left" id="delete_row">Delete</th>
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
|
||||
<h4 class="modal-title" id="myModalLabel">
|
||||
<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>
|
||||
</h4>
|
||||
</div>
|
||||
<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>
|
||||
<tr>
|
||||
<th align="left" id="started">Started</th>
|
||||
|
@ -32,7 +32,7 @@
|
|||
<script src="interfaces/default/js/tables/history_table_modal.js"></script>
|
||||
<script>
|
||||
$(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 = {
|
||||
url: 'get_history',
|
||||
type: 'post',
|
||||
|
@ -40,14 +40,16 @@
|
|||
return {
|
||||
json_data: JSON.stringify(d),
|
||||
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
|
||||
if (!($('#history-modal').next().is('#info-modal'))) {
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<h3>Watch Statistics <small>Last ${config['home_stats_length']} days</small></h3>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -36,19 +36,27 @@
|
|||
<h3>Library Statistics <small>${config['pms_name']}</small></h3>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
<div class='row'>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<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>
|
||||
</div>
|
||||
<div id='recentlyAdded'>
|
||||
<div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Looking for new items...</div>
|
||||
<div id="recentlyAdded" style="margin-right: -15px;">
|
||||
<div class="text-muted"><i class="fa fa-refresh fa-spin"></i> Looking for new items...</div>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -59,7 +67,6 @@
|
|||
<%def name="javascriptIncludes()">
|
||||
<script src="interfaces/default/js/moment-with-locale.js"></script>
|
||||
<script>
|
||||
|
||||
function currentActivityHeader() {
|
||||
$.ajax({
|
||||
url: 'get_current_activity_header',
|
||||
|
@ -71,7 +78,6 @@
|
|||
});
|
||||
}
|
||||
currentActivityHeader();
|
||||
setInterval(currentActivityHeader, 15000);
|
||||
|
||||
function currentActivity() {
|
||||
$.ajax({
|
||||
|
@ -84,7 +90,12 @@
|
|||
});
|
||||
}
|
||||
currentActivity();
|
||||
setInterval(currentActivity, 15000);
|
||||
|
||||
setInterval(function () {
|
||||
$('.bar, .bufferbar').tooltip('destroy');
|
||||
currentActivityHeader();
|
||||
currentActivity();
|
||||
}, 15000);
|
||||
|
||||
function getHomeStats(days) {
|
||||
$.ajax({
|
||||
|
@ -97,6 +108,7 @@
|
|||
}
|
||||
});
|
||||
}
|
||||
getHomeStats();
|
||||
|
||||
function getLibraryStats() {
|
||||
$.ajax({
|
||||
|
@ -109,33 +121,21 @@
|
|||
}
|
||||
});
|
||||
}
|
||||
getLibraryStats();
|
||||
|
||||
function recentlyAdded() {
|
||||
var widthVal = $('body').find(".container-fluid").width();
|
||||
var tmp = (widthVal-20) / 182;
|
||||
|
||||
if (tmp > 0) {
|
||||
containerSize = parseInt(tmp);
|
||||
} else {
|
||||
containerSize = 1;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: 'get_recently_added',
|
||||
type: "GET",
|
||||
async: true,
|
||||
data: { count : containerSize },
|
||||
data: { count : 50 },
|
||||
complete: function(xhr, status) {
|
||||
$("#recentlyAdded").html(xhr.responseText);
|
||||
highlightAddedScrollerButton();
|
||||
}
|
||||
});
|
||||
}
|
||||
$(document).ready(function () {
|
||||
recentlyAdded();
|
||||
$(window).resize(function() {
|
||||
recentlyAdded();
|
||||
});
|
||||
});
|
||||
recentlyAdded();
|
||||
|
||||
var date_format = 'YYYY-MM-DD';
|
||||
var time_format = 'hh:mm a';
|
||||
|
@ -148,10 +148,44 @@
|
|||
}
|
||||
});
|
||||
|
||||
getHomeStats();
|
||||
getLibraryStats();
|
||||
function highlightAddedScrollerButton() {
|
||||
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>
|
||||
|
||||
</%def>
|
||||
|
|
|
@ -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">
|
||||
<i class="fa fa-trash-o"></i> Delete mode
|
||||
</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> Select rows to delete. Data is deleted upon exiting delete mode.</div>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<tr>
|
||||
<th align='left' id="delete">Delete</th>
|
||||
<th align='left' id="time">Time</th>
|
||||
<th align='left' id="friendly_name">User</th>
|
||||
<th align='left' id="ip_address">IP Address</th>
|
||||
<th align='left' id="platform">Platform</th>
|
||||
<th align='left' id="player">Player</th>
|
||||
<th align='left' id="title">Title</th>
|
||||
<th align='left' id="started">Started</th>
|
||||
<th align='left' id="paused_counter">Paused</th>
|
||||
<th align='left' id="stopped">Stopped</th>
|
||||
<th align='left' id="duration">Duration</th>
|
||||
<th align='left' id="percent_complete"></th>
|
||||
<th align="left" id="delete">Delete</th>
|
||||
<th align="left" id="time">Time</th>
|
||||
<th align="left" id="friendly_name">User</th>
|
||||
<th align="left" id="ip_address">IP Address</th>
|
||||
<th align="left" id="platform">Platform</th>
|
||||
<th align="left" id="player">Player</th>
|
||||
<th align="left" id="title">Title</th>
|
||||
<th align="left" id="started">Started</th>
|
||||
<th align="left" id="paused_counter">Paused</th>
|
||||
<th align="left" id="stopped">Stopped</th>
|
||||
<th align="left" id="duration">Duration</th>
|
||||
<th align="left" id="percent_complete"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
|
@ -458,11 +474,11 @@ DOCUMENTATION :: END
|
|||
<script>
|
||||
$(document).ready(function () {
|
||||
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] });
|
||||
$(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-alert').fadeIn(200);
|
||||
|
@ -519,10 +535,33 @@ DOCUMENTATION :: END
|
|||
$("#runtime").html(millisecondsToMinutes($("#runtime").text(), true));
|
||||
$('div.art-face').animate({ opacity: 0.2 }, { duration: 1000 });
|
||||
</script>
|
||||
% if source == 'history':
|
||||
% if data.get('poster_url'):
|
||||
<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"> \
|
||||
<i class="fa fa-wrench"></i> Fix Metadata</a>');
|
||||
$('.imgur-poster-tooltip').popover({
|
||||
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> Imgur poster reset';
|
||||
showMsg(msg, false, true, 2000);
|
||||
$('.imgur-poster-tooltip').popover('destroy');
|
||||
$('#delete-imgur-poster').closest('span').remove();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
% endif
|
||||
% endif
|
||||
|
|
|
@ -32,7 +32,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<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>
|
||||
|
|
|
@ -28,8 +28,8 @@ var hc_plays_by_day_options = {
|
|||
cursor: 'pointer',
|
||||
point: {
|
||||
events: {
|
||||
click: function() {
|
||||
selectHandler(this.category);
|
||||
click: function () {
|
||||
selectHandler(this.category, this.series.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +60,8 @@ var hc_plays_by_day_options = {
|
|||
}
|
||||
},
|
||||
tooltip: {
|
||||
shared: true
|
||||
shared: true,
|
||||
crosshairs: true
|
||||
},
|
||||
series: [{}]
|
||||
};
|
|
@ -29,7 +29,7 @@ var hc_plays_by_stream_type_options = {
|
|||
point: {
|
||||
events: {
|
||||
click: function() {
|
||||
selectHandler(this.category);
|
||||
selectHandler(this.category, this.series.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +60,8 @@ var hc_plays_by_stream_type_options = {
|
|||
}
|
||||
},
|
||||
tooltip: {
|
||||
shared: true
|
||||
shared: true,
|
||||
crosshairs: true
|
||||
},
|
||||
series: [{}]
|
||||
};
|
|
@ -22,7 +22,7 @@ history_table_options = {
|
|||
"emptyTable": "No data in table"
|
||||
},
|
||||
"pagingType": "bootstrap",
|
||||
"stateSave": false,
|
||||
"stateSave": true,
|
||||
"processing": false,
|
||||
"serverSide": true,
|
||||
"pageLength": 25,
|
||||
|
@ -107,7 +107,7 @@ history_table_options = {
|
|||
}
|
||||
},
|
||||
"width": "10%",
|
||||
"className": "no-wrap hidden-md hidden-sm hidden-xs modal-control"
|
||||
"className": "no-wrap hidden-md hidden-sm hidden-xs"
|
||||
},
|
||||
{
|
||||
"targets": [5],
|
||||
|
@ -115,11 +115,11 @@ history_table_options = {
|
|||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
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>';
|
||||
} 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>';
|
||||
} 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>';
|
||||
}
|
||||
$(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + ' ' + cellData + '</div></a></div>');
|
||||
|
@ -307,7 +307,7 @@ history_table_options = {
|
|||
}
|
||||
|
||||
// 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 row = history_table.row( tr );
|
||||
var rowData = row.data();
|
||||
|
@ -327,7 +327,7 @@ $('#history_table').on('click', '> tbody > tr > td.modal-control', function () {
|
|||
});
|
||||
|
||||
// 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 row = history_table.row( tr );
|
||||
var rowData = row.data();
|
||||
|
@ -350,7 +350,7 @@ $('#history_table').on('click', '> tbody > tr > td.modal-control-ip', function (
|
|||
});
|
||||
|
||||
// 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 row = history_table.row( tr );
|
||||
var rowData = row.data();
|
||||
|
@ -399,7 +399,7 @@ $('#history_table').on('click', '> tbody > tr > td.delete-control > button', fun
|
|||
});
|
||||
|
||||
// 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 row = history_table.row(tr);
|
||||
var rowData = row.data();
|
||||
|
|
|
@ -79,11 +79,11 @@ history_table_modal_options = {
|
|||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
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>';
|
||||
} 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>';
|
||||
} 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>';
|
||||
}
|
||||
$(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + ' ' + 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 row = history_table.row(tr);
|
||||
var rowData = row.data();
|
||||
|
|
|
@ -16,7 +16,7 @@ libraries_list_table_options = {
|
|||
"pageLength": 10,
|
||||
"order": [ 2, 'asc'],
|
||||
"autoWidth": true,
|
||||
"stateSave": false,
|
||||
"stateSave": true,
|
||||
"pagingType": "bootstrap",
|
||||
"columnDefs": [
|
||||
{
|
||||
|
|
|
@ -5,7 +5,7 @@ var log_table_options = {
|
|||
"pagingType": "bootstrap",
|
||||
"order": [ 0, 'desc'],
|
||||
"pageLength": 50,
|
||||
"stateSave": false,
|
||||
"stateSave": true,
|
||||
"language": {
|
||||
"search":"Search: ",
|
||||
"lengthMenu":"Show _MENU_ lines per page",
|
||||
|
|
|
@ -23,7 +23,7 @@ media_info_table_options = {
|
|||
"emptyTable": "No data in table"
|
||||
},
|
||||
"pagingType": "bootstrap",
|
||||
"stateSave": false,
|
||||
"stateSave": true,
|
||||
"processing": false,
|
||||
"serverSide": true,
|
||||
"pageLength": 25,
|
||||
|
@ -294,7 +294,7 @@ media_info_table_options = {
|
|||
}
|
||||
|
||||
// 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 row = media_info_table.row(tr);
|
||||
var rowData = row.data();
|
||||
|
|
|
@ -5,7 +5,7 @@ notification_log_table_options = {
|
|||
"pagingType": "bootstrap",
|
||||
"order": [ 0, 'desc'],
|
||||
"pageLength": 50,
|
||||
"stateSave": false,
|
||||
"stateSave": true,
|
||||
"language": {
|
||||
"search":"Search: ",
|
||||
"lengthMenu":"Show _MENU_ lines per page",
|
||||
|
|
|
@ -5,7 +5,7 @@ var plex_log_table_options = {
|
|||
"pagingType": "bootstrap",
|
||||
"order": [ 0, 'desc'],
|
||||
"pageLength": 50,
|
||||
"stateSave": false,
|
||||
"stateSave": true,
|
||||
"language": {
|
||||
"search":"Search: ",
|
||||
"lengthMenu":"Show _MENU_ lines per page",
|
||||
|
|
|
@ -4,7 +4,7 @@ sync_table_options = {
|
|||
"pagingType": "bootstrap",
|
||||
"order": [ [ 0, 'desc'], [ 1, 'asc'], [2, 'asc'] ],
|
||||
"pageLength": 25,
|
||||
"stateSave": false,
|
||||
"stateSave": true,
|
||||
"language": {
|
||||
"search":"Search: ",
|
||||
"lengthMenu":"Show _MENU_ lines per page",
|
||||
|
|
|
@ -8,7 +8,7 @@ user_ip_table_options = {
|
|||
"infoFiltered":"(filtered from _MAX_ total entries)",
|
||||
"emptyTable": "No data in table",
|
||||
},
|
||||
"stateSave": false,
|
||||
"stateSave": true,
|
||||
"pagingType": "bootstrap",
|
||||
"processing": false,
|
||||
"serverSide": true,
|
||||
|
@ -56,7 +56,7 @@ user_ip_table_options = {
|
|||
}
|
||||
},
|
||||
"width": "15%",
|
||||
"className": "no-wrap hidden-md hidden-sm hidden-xs modal-control"
|
||||
"className": "no-wrap hidden-md hidden-sm hidden-xs"
|
||||
},
|
||||
{
|
||||
"targets": [3],
|
||||
|
@ -64,11 +64,11 @@ user_ip_table_options = {
|
|||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData) {
|
||||
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>';
|
||||
} 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>';
|
||||
} 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>';
|
||||
}
|
||||
$(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + ' ' + 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();
|
||||
});
|
||||
|
||||
$('#user_ip_table').on('click', 'td.modal-control', function () {
|
||||
$('.user_ip_table').on('click', 'td.modal-control', function () {
|
||||
var tr = $(this).parents('tr');
|
||||
var row = user_ip_table.row(tr);
|
||||
var rowData = row.data();
|
||||
|
@ -169,7 +169,7 @@ $('#user_ip_table').on('click', 'td.modal-control', function () {
|
|||
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 row = user_ip_table.row( tr );
|
||||
var rowData = row.data();
|
||||
|
|
|
@ -16,7 +16,7 @@ users_list_table_options = {
|
|||
"pageLength": 10,
|
||||
"order": [ 2, 'asc'],
|
||||
"autoWidth": true,
|
||||
"stateSave": false,
|
||||
"stateSave": true,
|
||||
"pagingType": "bootstrap",
|
||||
"columnDefs": [
|
||||
{
|
||||
|
@ -120,11 +120,11 @@ users_list_table_options = {
|
|||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== null && cellData !== '') {
|
||||
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>';
|
||||
} 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>';
|
||||
} 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>';
|
||||
}
|
||||
$(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + ' ' + cellData + '</div></a></div>');
|
||||
|
|
|
@ -118,8 +118,16 @@ DOCUMENTATION :: END
|
|||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<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">
|
||||
<span><i class="fa fa-history"></i> Recently Watched</span>
|
||||
<span><i class="fa fa-history"></i> Recently Played</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-card-back">
|
||||
|
@ -135,6 +143,14 @@ DOCUMENTATION :: END
|
|||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<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">
|
||||
<span><i class="fa fa-history"></i> Recently Added</span>
|
||||
</div>
|
||||
|
@ -170,7 +186,7 @@ DOCUMENTATION :: END
|
|||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<tr>
|
||||
<th align="left" id="delete">Delete</th>
|
||||
|
@ -229,7 +245,7 @@ DOCUMENTATION :: END
|
|||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<tr>
|
||||
<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] });
|
||||
$(colvis.button()).appendTo('#button-bar-history');
|
||||
|
||||
clearSearchButton('history_table', history_table);
|
||||
clearSearchButton('history_table-SID-${data["section_id"]}', history_table);
|
||||
}
|
||||
|
||||
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' });
|
||||
$(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() {
|
||||
|
@ -460,61 +476,116 @@ DOCUMENTATION :: END
|
|||
});
|
||||
|
||||
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
|
||||
$.ajax({
|
||||
url: 'get_library_recently_watched',
|
||||
async: true,
|
||||
data: {
|
||||
section_id: section_id,
|
||||
limit: containerSize
|
||||
limit: 50
|
||||
},
|
||||
complete: function(xhr, status) {
|
||||
$("#library-recently-watched").html(xhr.responseText);
|
||||
highlightWatchedScrollerButton();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
$.ajax({
|
||||
url: 'get_library_recently_added',
|
||||
async: true,
|
||||
data: {
|
||||
section_id: section_id,
|
||||
limit: containerSize
|
||||
limit: 50
|
||||
},
|
||||
complete: function(xhr, status) {
|
||||
$("#library-recently-added").html(xhr.responseText);
|
||||
highlightAddedScrollerButton();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
recentlyWatched();
|
||||
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() {
|
||||
recentlyWatched();
|
||||
recentlyAdded();
|
||||
highlightWatchedScrollerButton();
|
||||
highlightAddedScrollerButton();
|
||||
});
|
||||
|
||||
$('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>
|
||||
% endif
|
||||
|
|
|
@ -32,63 +32,65 @@ DOCUMENTATION :: END
|
|||
|
||||
% if data:
|
||||
<div class="dashboard-recent-media-row">
|
||||
<ul class="dashboard-recent-media list-unstyled">
|
||||
% for item in data:
|
||||
<li>
|
||||
% if item['media_type'] == 'episode' or item['media_type'] == 'movie':
|
||||
<a href="info?rating_key=${item['rating_key']}">
|
||||
<div class="dashboard-recent-media-poster">
|
||||
% if item['media_type'] == 'episode':
|
||||
% if item['parent_thumb']:
|
||||
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['parent_thumb']}&width=300&height=450&fallback=poster);">
|
||||
% else:
|
||||
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['grandparent_thumb']}&width=300&height=450&fallback=poster);">
|
||||
% endif
|
||||
% elif item['media_type'] == 'movie':
|
||||
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=450&fallback=poster);">
|
||||
% endif
|
||||
<div class="dashboard-recent-media-overlay">
|
||||
<div class="dashboard-recent-media-overlay-text" id="added_at-${item['rating_key']}">
|
||||
<script>
|
||||
<div id="recently-added-row-scroller" style="left: 0;">
|
||||
<ul class="dashboard-recent-media list-unstyled">
|
||||
% for item in data:
|
||||
<li>
|
||||
% if item['media_type'] == 'episode' or item['media_type'] == 'movie':
|
||||
<a href="info?rating_key=${item['rating_key']}">
|
||||
<div class="dashboard-recent-media-poster">
|
||||
% if item['media_type'] == 'episode':
|
||||
% if item['parent_thumb']:
|
||||
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['parent_thumb']}&width=300&height=450&fallback=poster);">
|
||||
% else:
|
||||
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['grandparent_thumb']}&width=300&height=450&fallback=poster);">
|
||||
% endif
|
||||
% elif item['media_type'] == 'movie':
|
||||
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=450&fallback=poster);">
|
||||
% endif
|
||||
<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 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']} · 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>
|
||||
</script>
|
||||
</div>
|
||||
</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']} · 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 class="dashboard-recent-media-metacontainer">
|
||||
<h3 title="${item['parent_title']}">${item['parent_title']}</h3>
|
||||
<h3 class="text-muted">${item['title']}</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-recent-media-metacontainer">
|
||||
<h3 title="${item['parent_title']}">${item['parent_title']}</h3>
|
||||
<h3 class="text-muted">${item['title']}</h3>
|
||||
</div>
|
||||
</a>
|
||||
% endif
|
||||
</li>
|
||||
% endfor
|
||||
</ul>
|
||||
</a>
|
||||
% endif
|
||||
</li>
|
||||
% endfor
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
% else:
|
||||
<div class="text-muted">No stats to show.
|
||||
|
|
|
@ -22,6 +22,7 @@ from plexpy import helpers
|
|||
</div>
|
||||
<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-notify-logs" style="display: none;"><i class="fa fa-trash-o"></i> Clear log</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class='table-card-back'>
|
||||
|
@ -156,24 +157,28 @@ from plexpy import helpers
|
|||
|
||||
$("#plexpy-logs-btn").click(function () {
|
||||
$("#clear-logs").show();
|
||||
$("#clear-notify-logs").hide();
|
||||
LoadPlexPyLogs();
|
||||
clearSearchButton('log_table', log_table);
|
||||
});
|
||||
|
||||
$("#plex-logs-btn").click(function () {
|
||||
$("#clear-logs").hide();
|
||||
$("#clear-notify-logs").hide();
|
||||
LoadPlexLogs();
|
||||
clearSearchButton('plex_log_table', plex_log_table);
|
||||
});
|
||||
|
||||
$("#plex-scanner-logs-btn").click(function () {
|
||||
$("#clear-logs").hide();
|
||||
$("#clear-notify-logs").hide();
|
||||
LoadPlexScannerLogs();
|
||||
clearSearchButton('plex_scanner_log_table', plex_scanner_log_table);
|
||||
});
|
||||
|
||||
$("#notification-logs-btn").click(function () {
|
||||
$("#clear-logs").hide();
|
||||
$("#clear-notify-logs").show();
|
||||
LoadNotificationLogs();
|
||||
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;
|
||||
function setRefresh()
|
||||
{
|
||||
|
@ -200,8 +218,12 @@ from plexpy import helpers
|
|||
timer = setInterval(function() {
|
||||
if ($("#tabs-1").hasClass("active")) {
|
||||
log_table.ajax.reload();
|
||||
} else {
|
||||
} else if ($("#tabs-2").hasClass("active")) {
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -92,6 +92,13 @@ from plexpy import helpers
|
|||
</label>
|
||||
<p class="help-block">Trigger notification when the Plex Media Server can be reached externally after being down.</p>
|
||||
</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>
|
||||
|
|
|
@ -32,56 +32,58 @@ DOCUMENTATION :: END
|
|||
|
||||
% if data != None:
|
||||
<div class="dashboard-recent-media-row">
|
||||
<ul class="dashboard-recent-media list-unstyled">
|
||||
% for item in data:
|
||||
<div class="dashboard-recent-media-instance">
|
||||
<li>
|
||||
% if item['media_type'] == 'season' or item['media_type'] == 'movie':
|
||||
<a href="info?rating_key=${item['rating_key']}">
|
||||
<div class="dashboard-recent-media-poster">
|
||||
<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">
|
||||
<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 id="recently-added-row-scroller" style="left: 0;">
|
||||
<ul class="dashboard-recent-media list-unstyled">
|
||||
% for item in data:
|
||||
<div class="dashboard-recent-media-instance">
|
||||
<li>
|
||||
% if item['media_type'] == 'season' or item['media_type'] == 'movie':
|
||||
<a href="info?rating_key=${item['rating_key']}">
|
||||
<div class="dashboard-recent-media-poster">
|
||||
<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">
|
||||
<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>
|
||||
<div class="dashboard-recent-media-metacontainer">
|
||||
% if item['media_type'] == 'season':
|
||||
<h3 title="${item['parent_title']}">${item['parent_title']}</h3>
|
||||
<h3 class="text-muted">${item['title']}</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 class="dashboard-recent-media-metacontainer">
|
||||
% if item['media_type'] == 'season':
|
||||
<h3 title="${item['parent_title']}">${item['parent_title']}</h3>
|
||||
<h3 class="text-muted">${item['title']}</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>
|
||||
<div class="dashboard-recent-media-metacontainer">
|
||||
<h3 title="${item['parent_title']}">${item['parent_title']}</h3>
|
||||
<h3 class="text-muted">${item['title']}</h3>
|
||||
</div>
|
||||
</a>
|
||||
% endif
|
||||
</li>
|
||||
</div>
|
||||
% endfor
|
||||
</ul>
|
||||
<div class="dashboard-recent-media-metacontainer">
|
||||
<h3 title="${item['parent_title']}">${item['parent_title']}</h3>
|
||||
<h3 class="text-muted">${item['title']}</h3>
|
||||
</div>
|
||||
</a>
|
||||
% endif
|
||||
</li>
|
||||
</div>
|
||||
% endfor
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
% else:
|
||||
<div class="text-muted">There was an error communicating with your Plex Server. Please check your <a href="settings">settings</a>.
|
||||
|
|
|
@ -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-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-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-10" aria-controls="tabs-10" role="tab" data-toggle="tab">Notification Agents</a></li>
|
||||
</ul>
|
||||
|
@ -82,15 +82,15 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
|||
</tr>
|
||||
<tr>
|
||||
<td>Backup Directory:</td>
|
||||
<td>${plexpy.CONFIG.BACKUP_DIR}</td>
|
||||
<td>${config['backup_dir']}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cache Directory:</td>
|
||||
<td>${plexpy.CONFIG.CACHE_DIR}</td>
|
||||
<td>${config['cache_dir']}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Log Directory:</td>
|
||||
<td>${plexpy.CONFIG.LOG_DIR}</td>
|
||||
<td>${config['log_dir']}</td>
|
||||
</tr>
|
||||
% if plexpy.ARGS:
|
||||
<tr>
|
||||
|
@ -169,6 +169,35 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
|||
</label>
|
||||
<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 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>
|
||||
</div>
|
||||
<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 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>
|
||||
<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">
|
||||
<label for="pms_ip">Plex IP or Hostname</label>
|
||||
<div class="row">
|
||||
|
@ -485,8 +529,9 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
|||
<label for="pms_logs_folder">Logs Folder</label>
|
||||
<div class="row">
|
||||
<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 id="pms_logs_folder_error" class="alert alert-danger settings-alert" role="alert"></div>
|
||||
</div>
|
||||
<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>
|
||||
|
@ -613,13 +658,6 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
|||
</label>
|
||||
<p class="help-block">Instead of polling the server at regular intervals let the server tell PlexPy when something happens.</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="padded-header">
|
||||
<h3>History Logging</h3>
|
||||
|
@ -749,8 +787,14 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
|||
<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
|
||||
</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 />
|
||||
Note: No Season/Episode or Album/Track metadata will be available.</p>
|
||||
<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 />
|
||||
% 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 class="form-group">
|
||||
<label for="notify_recently_added_delay">Notification Delay</label>
|
||||
|
@ -965,6 +1009,23 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
|||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<div class="link"><i class="fa fa-refresh fa-fw"></i> 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 id="accordion-scripts" class="accordion list-unstyled">
|
||||
<li>
|
||||
|
@ -1025,24 +1086,45 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
|||
<h4 class="modal-title">Date & Time Format Options</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<table>
|
||||
<table class="notification-params time-options">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Year
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" colspan="3"><h5>Year</h5></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>YYYY</strong></td>
|
||||
<td>Numeric, 4 digits</td>
|
||||
<td>Numeric, four digits</td>
|
||||
<td>Eg., 1999, 2003</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>YY</strong></td>
|
||||
<td>Numeric, 2 digits</td>
|
||||
<td>Numeric, two digits</td>
|
||||
<td>Eg., 99, 03</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="notification-params time-options">
|
||||
<thead>
|
||||
<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>
|
||||
<td><strong>MM</strong></td>
|
||||
|
@ -1054,23 +1136,41 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
|||
<td>Numeric, without leading zeros</td>
|
||||
<td>1-12</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="notification-params time-options">
|
||||
<thead>
|
||||
<tr>
|
||||
<td><strong>MMMM</strong></td>
|
||||
<td>Textual full</td>
|
||||
<td>January-December</td>
|
||||
<th>
|
||||
Day of the Year
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>DDDD</strong></td>
|
||||
<td>Numeric, with leading zeros</td>
|
||||
<td>001-365</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>MMM</strong></td>
|
||||
<td>Textual three letters</td>
|
||||
<td>Jan-Dec</td>
|
||||
<td><strong>DDD</strong></td>
|
||||
<td>Numeric, without leading zeros</td>
|
||||
<td>1-365</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="notification-params time-options">
|
||||
<thead>
|
||||
<tr>
|
||||
<td align="center" colspan="3"><h5>Day</h5></td>
|
||||
<th>
|
||||
Day of the Month
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="100"><strong>DD</strong></td>
|
||||
<td width="300">Numeric, with leading zeros</td>
|
||||
<td><strong>DD</strong></td>
|
||||
<td>Numeric, with leading zeros</td>
|
||||
<td>01-31</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -1080,57 +1180,165 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
|||
</tr>
|
||||
<tr>
|
||||
<td><strong>Do</strong></td>
|
||||
<td>The English suffix for the day of the month</td>
|
||||
<td>st, nd or th in the 1st, 2nd or 15th.</td>
|
||||
<td>Numeric, with suffix</td>
|
||||
<td>Eg., 1st, 2nd ... 31st.</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="notification-params time-options">
|
||||
<thead>
|
||||
<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>
|
||||
<td><strong>a</strong></td>
|
||||
<td width="300">am/pm Lowercase</td>
|
||||
<td>am, pm</td>
|
||||
<td><strong>ddd</strong></td>
|
||||
<td>Textual, three letters</td>
|
||||
<td>Sun-Sat</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>A</strong></td>
|
||||
<td>AM/PM Uppercase</td>
|
||||
<td>AM, PM</td>
|
||||
<td><strong>d</strong></td>
|
||||
<td>Numeric</td>
|
||||
<td>0-6</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="notification-params time-options">
|
||||
<thead>
|
||||
<tr>
|
||||
<td><strong>h</strong></td>
|
||||
<td>Hour, 12-hour, without leading zeros</td>
|
||||
<td>1-12</td>
|
||||
</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>
|
||||
<th>
|
||||
Hour
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>HH</strong></td>
|
||||
<td>Hour, 24-hour, with leading zeros</td>
|
||||
<td>24-hour, with leading zeros</td>
|
||||
<td>00-23</td>
|
||||
</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>
|
||||
<td><strong>mm</strong></td>
|
||||
<td>Minutes, with leading zeros</td>
|
||||
<td>Numeric, with leading zeros</td>
|
||||
<td>00-59</td>
|
||||
</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>
|
||||
<td><strong>ss</strong></td>
|
||||
<td>Seconds, with leading zeros</td>
|
||||
<td>Numeric, with leading zeros</td>
|
||||
<td>00-59</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>zz</strong></td>
|
||||
<td>Timezone abbreviation</td>
|
||||
<td>Eg., EST, MDT ...</td>
|
||||
<td><strong>s</strong></td>
|
||||
<td>Numeric, without leading zeros</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>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -1215,6 +1423,10 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
|||
<td><strong>{server_uptime}</strong></td>
|
||||
<td>The uptime (in days, hours, mins, secs) of your Plex Server.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{server_version}</strong></td>
|
||||
<td>The current version of your Plex Server.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{action}</strong></td>
|
||||
<td>The action that triggered the notification.</td>
|
||||
|
@ -1407,7 +1619,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
|||
</tr>
|
||||
<tr>
|
||||
<td><strong>{artist_name}</strong></td>
|
||||
<td>The name of the artistd.</td>
|
||||
<td>The name of the artist.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{album_name}</strong></td>
|
||||
|
@ -1521,7 +1733,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
|||
</tr>
|
||||
<tr>
|
||||
<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>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -1546,6 +1758,29 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
|||
</tr>
|
||||
</tbody>
|
||||
</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 class="modal-footer"></div>
|
||||
|
@ -1650,10 +1885,11 @@ $(document).ready(function() {
|
|||
authChanged = false;
|
||||
httpChanged = false;
|
||||
monitorChanged = false;
|
||||
directoryChanged = false;
|
||||
|
||||
// Alert the user that their changes require a restart.
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
@ -1766,6 +2002,10 @@ $(document).ready(function() {
|
|||
monitorChanged = true;
|
||||
});
|
||||
|
||||
$( ".directory-settings" ).change(function() {
|
||||
directoryChanged = true;
|
||||
});
|
||||
|
||||
$( ".pms-settings" ).change(function() {
|
||||
serverChanged = true;
|
||||
$("#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); });
|
||||
})
|
||||
|
||||
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({
|
||||
url: 'get_server_identity',
|
||||
async: true,
|
||||
success: function(data) {
|
||||
var version = data.version.split('.')
|
||||
if (parseInt(version[0]) >= 0 && parseInt(version[1]) >= 9 && parseInt(version[2]) >= 14) {
|
||||
if (data.version){ $("#pms_version").text(data.version); }
|
||||
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.");
|
||||
$("#ip_logging_enable").attr("disabled", true);
|
||||
$("#ip_logging_enable").attr("checked", true);
|
||||
pms_version = true;
|
||||
checkLogsPath();
|
||||
} else {
|
||||
// Check to see if debug logs are enabled on the PMS.
|
||||
$.ajax({
|
||||
url: 'get_server_pref',
|
||||
data: { pref: 'logDebug' },
|
||||
async: true,
|
||||
success: function(data) {
|
||||
if (data !== 'true') {
|
||||
$("#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);
|
||||
}
|
||||
pms_logs_debug = (data == 'true' ? true : false);
|
||||
// Check to see if our logs folder is set before allowing IP logging to be enabled.
|
||||
checkLogsPath();
|
||||
}
|
||||
});
|
||||
|
||||
// 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({
|
||||
url: 'get_server_pref',
|
||||
data: { pref: 'PublishServerOnPlexOnlineKey' },
|
||||
|
@ -2055,6 +2307,10 @@ $(document).ready(function() {
|
|||
}
|
||||
getSchedulerTable();
|
||||
|
||||
$("#notify_recently_added_grandparent").change(function () {
|
||||
var c = this.checked ? '#eb8600' : '#737373';
|
||||
$('#notify_recently_added_grandparent_note').css('color', c);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</%def>
|
|
@ -112,8 +112,16 @@ from plexpy import helpers
|
|||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<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">
|
||||
<span><i class="fa fa-history"></i> Recently Watched</span>
|
||||
<span><i class="fa fa-history"></i> Recently Played</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-card-back">
|
||||
|
@ -140,7 +148,7 @@ from plexpy import helpers
|
|||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<tr>
|
||||
<th align="left">Last Seen</th>
|
||||
|
@ -178,7 +186,7 @@ from plexpy import helpers
|
|||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<tr>
|
||||
<th align="left" id="delete">Delete</th>
|
||||
|
@ -218,7 +226,7 @@ from plexpy import helpers
|
|||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<tr>
|
||||
<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);
|
||||
|
||||
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');
|
||||
|
||||
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;"> \
|
||||
<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() {
|
||||
|
@ -410,13 +418,13 @@ from plexpy import helpers
|
|||
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);
|
||||
|
||||
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');
|
||||
|
||||
clearSearchButton('sync_table', sync_table);
|
||||
clearSearchButton('sync_table-UID-${data["user_id"]}', sync_table);
|
||||
});
|
||||
|
||||
// Load edit user modal
|
||||
|
@ -471,32 +479,60 @@ from plexpy import helpers
|
|||
});
|
||||
|
||||
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
|
||||
$.ajax({
|
||||
url: 'get_user_recently_watched',
|
||||
async: true,
|
||||
data: {
|
||||
user_id: user_id,
|
||||
limit: containerSize
|
||||
limit: 50
|
||||
},
|
||||
complete: function(xhr, status) {
|
||||
$("#user-recently-watched").html(xhr.responseText);
|
||||
highlightWatchedScrollerButton();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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() {
|
||||
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>
|
||||
|
|
|
@ -28,56 +28,58 @@ DOCUMENTATION :: END
|
|||
|
||||
% if data:
|
||||
<div class="dashboard-recent-media-row">
|
||||
<ul class="dashboard-recent-media list-unstyled">
|
||||
% for item in data:
|
||||
<li>
|
||||
% if item['media_type'] == 'episode' or item['media_type'] == 'movie':
|
||||
<a href="info?source=history&rating_key=${item['rating_key']}">
|
||||
<div class="dashboard-recent-media-poster">
|
||||
<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">
|
||||
<div class="dashboard-recent-media-overlay-text" id="time-${item['time']}">
|
||||
<script>
|
||||
$('#time-${item['time']}').text('Watched ' + moment(${item['time']}, "X").fromNow())
|
||||
</script>
|
||||
<div id="recently-watched-row-scroller" style="left: 0;">
|
||||
<ul class="dashboard-recent-media list-unstyled">
|
||||
% for item in data:
|
||||
<li>
|
||||
% if item['media_type'] == 'episode' or item['media_type'] == 'movie':
|
||||
<a href="info?source=history&rating_key=${item['rating_key']}">
|
||||
<div class="dashboard-recent-media-poster">
|
||||
<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">
|
||||
<div class="dashboard-recent-media-overlay-text" id="time-${item['time']}">
|
||||
<script>
|
||||
$('#time-${item['time']}').text('Watched ' + moment(${item['time']}, "X").fromNow())
|
||||
</script>
|
||||
</div>
|
||||
</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']} · 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'] == 'track':
|
||||
<a href="info?source=history&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="time-${item['time']}">
|
||||
<script>
|
||||
$('#time-${item['time']}').text('Watched ' + moment(${item['time']}, "X").fromNow())
|
||||
</script>
|
||||
<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']} · 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'] == 'track':
|
||||
<a href="info?source=history&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="time-${item['time']}">
|
||||
<script>
|
||||
$('#time-${item['time']}').text('Watched ' + moment(${item['time']}, "X").fromNow())
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-recent-media-metacontainer">
|
||||
<h3 title="${item['grandparent_title']}">${item['grandparent_title']}</h3>
|
||||
<h3 title="${item['title']}">${item['title']}</h3>
|
||||
<h3 class="text-muted">${item['parent_title']}</h3>
|
||||
</div>
|
||||
</a>
|
||||
% endif
|
||||
</li>
|
||||
% endfor
|
||||
</ul>
|
||||
<div class="dashboard-recent-media-metacontainer">
|
||||
<h3 title="${item['grandparent_title']}">${item['grandparent_title']}</h3>
|
||||
<h3 title="${item['title']}">${item['title']}</h3>
|
||||
<h3 class="text-muted">${item['parent_title']}</h3>
|
||||
</div>
|
||||
</a>
|
||||
% endif
|
||||
</li>
|
||||
% endfor
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
% else:
|
||||
<div class="text-muted">No stats to show.</div><br>
|
||||
|
|
|
@ -33,12 +33,13 @@ load_rc_config ${name}
|
|||
: ${plexpy_dir:="/usr/local/plexpy"}
|
||||
: ${plexpy_chdir:="${plexpy_dir}"}
|
||||
: ${plexpy_pid:="${plexpy_dir}/plexpy.pid"}
|
||||
: ${plexpy_flags:=""}
|
||||
|
||||
status_cmd="${name}_status"
|
||||
stop_cmd="${name}_stop"
|
||||
|
||||
command="/usr/sbin/daemon"
|
||||
command_args="python2 ${plexpy_dir}/PlexPy.py --daemon --pidfile ${plexpy_pid} --quiet --nolaunch"
|
||||
command="${plexpy_dir}/PlexPy.py"
|
||||
command_args="--daemon --pidfile ${plexpy_pid} --quiet --nolaunch ${plexpy_flags}"
|
||||
|
||||
# Ensure user is root when running this script.
|
||||
if [ `id -u` != "0" ]; then
|
||||
|
|
5
init-scripts/init.freenas
Normal file → Executable file
5
init-scripts/init.freenas
Normal file → Executable file
|
@ -33,12 +33,13 @@ load_rc_config ${name}
|
|||
: ${plexpy_dir:="/usr/local/share/plexpy"}
|
||||
: ${plexpy_chdir:="${plexpy_dir}"}
|
||||
: ${plexpy_pid:="${plexpy_dir}/plexpy.pid"}
|
||||
: ${plexpy_flags:=""}
|
||||
|
||||
status_cmd="${name}_status"
|
||||
stop_cmd="${name}_stop"
|
||||
|
||||
command="/usr/sbin/daemon"
|
||||
command_args="python2 ${plexpy_dir}/PlexPy.py --daemon --pidfile ${plexpy_pid} --quiet --nolaunch"
|
||||
command="${plexpy_dir}/PlexPy.py"
|
||||
command_args="--daemon --pidfile ${plexpy_pid} --quiet --nolaunch ${plexpy_flags}"
|
||||
|
||||
# Ensure user is root when running this script.
|
||||
if [ `id -u` != "0" ]; then
|
||||
|
|
|
@ -103,7 +103,7 @@ def initialize(config_file):
|
|||
if not CONFIG.HTTPS_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')
|
||||
|
||||
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,
|
||||
verbose=VERBOSE)
|
||||
|
||||
if not CONFIG.BACKUP_DIR.startswith(os.path.abspath(DATA_DIR)):
|
||||
# Put the backup dir in the data dir for now
|
||||
if not CONFIG.BACKUP_DIR:
|
||||
CONFIG.BACKUP_DIR = os.path.join(DATA_DIR, 'backups')
|
||||
if not os.path.exists(CONFIG.BACKUP_DIR):
|
||||
try:
|
||||
|
@ -129,14 +128,13 @@ def initialize(config_file):
|
|||
except OSError as e:
|
||||
logger.error("Could not create backup dir '%s': %s", BACKUP_DIR, e)
|
||||
|
||||
if not CONFIG.CACHE_DIR.startswith(os.path.abspath(DATA_DIR)):
|
||||
# Put the cache dir in the data dir for now
|
||||
if not CONFIG.CACHE_DIR:
|
||||
CONFIG.CACHE_DIR = os.path.join(DATA_DIR, 'cache')
|
||||
if not os.path.exists(CONFIG.CACHE_DIR):
|
||||
try:
|
||||
os.makedirs(CONFIG.CACHE_DIR)
|
||||
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
|
||||
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',
|
||||
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:
|
||||
schedule_job(activity_pinger.check_server_response, 'Check for Plex remote access',
|
||||
hours=0, minutes=0, seconds=seconds)
|
||||
|
@ -390,7 +395,7 @@ def dbcheck():
|
|||
# sessions table :: This is a temp table that logs currently active sessions
|
||||
c_db.execute(
|
||||
'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, '
|
||||
'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, '
|
||||
|
@ -413,8 +418,8 @@ def dbcheck():
|
|||
|
||||
# session_history_media_info table :: This is a table which logs each session's media info
|
||||
c_db.execute(
|
||||
'CREATE TABLE IF NOT EXISTS session_history_media_info (id INTEGER PRIMARY KEY, '
|
||||
'rating_key INTEGER, video_decision TEXT, audio_decision TEXT, duration INTEGER DEFAULT 0, width INTEGER, '
|
||||
'CREATE TABLE IF NOT EXISTS session_history_media_info (id INTEGER PRIMARY KEY, rating_key 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, '
|
||||
'video_framerate TEXT, aspect_ratio TEXT, audio_channels INTEGER, transcode_protocol 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'
|
||||
)
|
||||
|
||||
# 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
|
||||
try:
|
||||
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'
|
||||
)
|
||||
|
||||
# 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
|
||||
try:
|
||||
c_db.execute('SELECT do_notify FROM users')
|
||||
|
@ -890,7 +919,13 @@ def shutdown(restart=False, update=False):
|
|||
if '--nolaunch' not in args:
|
||||
args += ['--nolaunch']
|
||||
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)
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
import time
|
||||
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):
|
||||
|
@ -57,9 +57,11 @@ class ActivityHandler(object):
|
|||
if self.is_valid_session() and self.get_live_session():
|
||||
logger.debug(u"PlexPy ActivityHandler :: Session %s has started." % str(self.get_session_key()))
|
||||
|
||||
# Fire off notifications
|
||||
threading.Thread(target=notification_handler.notify,
|
||||
kwargs=dict(stream_data=self.get_live_session(), notify_action='play')).start()
|
||||
# Check if any notification agents have notifications enabled
|
||||
if any(d['on_play'] for d in notifiers.available_notification_agents()):
|
||||
# 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
|
||||
self.update_db_session()
|
||||
|
@ -77,20 +79,24 @@ class ActivityHandler(object):
|
|||
if not force_stop:
|
||||
ap.set_session_state(session_key=self.get_session_key(),
|
||||
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
|
||||
db_session = ap.get_session_by_key(session_key=self.get_session_key())
|
||||
|
||||
# Fire off notifications
|
||||
threading.Thread(target=notification_handler.notify,
|
||||
kwargs=dict(stream_data=db_session, notify_action='stop')).start()
|
||||
# Check if any notification agents have notifications enabled
|
||||
if any(d['on_stop'] for d in notifiers.available_notification_agents()):
|
||||
# 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
|
||||
monitor_proc = activity_processor.ActivityProcessor()
|
||||
monitor_proc.write_session_history(session=db_session)
|
||||
|
||||
# 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())
|
||||
|
||||
def on_pause(self):
|
||||
|
@ -109,9 +115,11 @@ class ActivityHandler(object):
|
|||
# Retrieve the session data from our temp table
|
||||
db_session = ap.get_session_by_key(session_key=self.get_session_key())
|
||||
|
||||
# Fire off notifications
|
||||
threading.Thread(target=notification_handler.notify,
|
||||
kwargs=dict(stream_data=db_session, notify_action='pause')).start()
|
||||
# Check if any notification agents have notifications enabled
|
||||
if any(d['on_pause'] for d in notifiers.available_notification_agents()):
|
||||
# Fire off notifications
|
||||
threading.Thread(target=notification_handler.notify,
|
||||
kwargs=dict(stream_data=db_session, notify_action='pause')).start()
|
||||
|
||||
def on_resume(self):
|
||||
if self.is_valid_session():
|
||||
|
@ -129,9 +137,11 @@ class ActivityHandler(object):
|
|||
# Retrieve the session data from our temp table
|
||||
db_session = ap.get_session_by_key(session_key=self.get_session_key())
|
||||
|
||||
# Fire off notifications
|
||||
threading.Thread(target=notification_handler.notify,
|
||||
kwargs=dict(stream_data=db_session, notify_action='resume')).start()
|
||||
# Check if any notification agents have notifications enabled
|
||||
if any(d['on_resume'] for d in notifiers.available_notification_agents()):
|
||||
# Fire off notifications
|
||||
threading.Thread(target=notification_handler.notify,
|
||||
kwargs=dict(stream_data=db_session, notify_action='resume')).start()
|
||||
|
||||
def on_buffer(self):
|
||||
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 \
|
||||
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())
|
||||
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
|
||||
def process(self):
|
||||
|
@ -202,10 +215,17 @@ class ActivityHandler(object):
|
|||
|
||||
# Monitor if the stream has reached the watch percentage for notifications
|
||||
# The only purpose of this is for notifications
|
||||
progress_percent = helpers.get_percent(self.timeline['viewOffset'], db_session['duration'])
|
||||
if progress_percent >= plexpy.CONFIG.NOTIFY_WATCHED_PERCENT and this_state != 'buffering':
|
||||
threading.Thread(target=notification_handler.notify,
|
||||
kwargs=dict(stream_data=db_session, notify_action='watched')).start()
|
||||
# Check if any notification agents have notifications enabled
|
||||
notify_agents = [d['id'] for d in notifiers.available_notification_agents() if d['on_watched']]
|
||||
# Get the current states for notifications from our db
|
||||
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:
|
||||
# We don't have this session in our table yet, start a new one.
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# 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 plexpy
|
||||
|
@ -38,9 +38,12 @@ def check_active_sessions(ws_request=False):
|
|||
if session_list:
|
||||
if int_ping_count >= 3:
|
||||
logger.info(u"PlexPy Monitor :: The Plex Media Server is back up.")
|
||||
# Fire off notifications
|
||||
threading.Thread(target=notification_handler.notify_timeline,
|
||||
kwargs=dict(notify_action='intup')).start()
|
||||
|
||||
# Check if any notification agents have notifications enabled
|
||||
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
|
||||
|
||||
media_container = session_list['sessions']
|
||||
|
@ -58,16 +61,24 @@ def check_active_sessions(ws_request=False):
|
|||
# Here we can check the play states
|
||||
if session['state'] != stream['state']:
|
||||
if session['state'] == 'paused':
|
||||
# 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()
|
||||
logger.debug(u"PlexPy Monitor :: Session %s has been paused." % stream['session_key'])
|
||||
|
||||
# Check if any notification agents have notifications enabled
|
||||
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':
|
||||
# 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()
|
||||
logger.debug(u"PlexPy Monitor :: Session %s has been resumed." % stream['session_key'])
|
||||
|
||||
# Check if any notification agents have notifications enabled
|
||||
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:
|
||||
# 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 = ?',
|
||||
[stream['session_key'], stream['rating_key']])
|
||||
|
||||
threading.Thread(target=notification_handler.notify,
|
||||
kwargs=dict(stream_data=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()):
|
||||
# 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:
|
||||
# Subsequent buffer notifications after wait time
|
||||
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 = ?',
|
||||
[stream['session_key'], stream['rating_key']])
|
||||
|
||||
threading.Thread(target=notification_handler.notify,
|
||||
kwargs=dict(stream_data=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()):
|
||||
# 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."
|
||||
% (buffer_values[0]['buffer_count'],
|
||||
logger.debug(u"PlexPy Monitor :: Session %s is buffering. Count is now %s. Last triggered %s."
|
||||
% (stream['session_key'],
|
||||
buffer_values[0]['buffer_count'],
|
||||
buffer_values[0]['buffer_last_triggered']))
|
||||
|
||||
# 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 helpers.get_percent(session['view_offset'],
|
||||
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 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
|
||||
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']])
|
||||
|
||||
# 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
|
||||
# Check if any notification agents have notifications enabled
|
||||
if any(d['on_stop'] 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()
|
||||
|
||||
# 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()
|
||||
kwargs=dict(stream_data=stream, notify_action='stop')).start()
|
||||
|
||||
# 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
|
||||
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:
|
||||
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))
|
||||
|
||||
if int_ping_count == 3:
|
||||
# Fire off notifications
|
||||
threading.Thread(target=notification_handler.notify_timeline,
|
||||
kwargs=dict(notify_action='intdown')).start()
|
||||
# Check if any notification agents have notifications enabled
|
||||
if any(d['on_intdown'] for d in notifiers.available_notification_agents()):
|
||||
# Fire off notifications
|
||||
threading.Thread(target=notification_handler.notify_timeline,
|
||||
kwargs=dict(notify_action='intdown')).start()
|
||||
|
||||
|
||||
def check_recently_added():
|
||||
|
@ -225,9 +271,12 @@ def check_recently_added():
|
|||
|
||||
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']))
|
||||
# Fire off notifications
|
||||
threading.Thread(target=notification_handler.notify_timeline,
|
||||
kwargs=dict(timeline_data=item, notify_action='created')).start()
|
||||
|
||||
# Check if any notification agents have notifications enabled
|
||||
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:
|
||||
item = max(metadata, key=lambda x:x['added_at'])
|
||||
|
@ -243,9 +292,12 @@ def check_recently_added():
|
|||
% 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,
|
||||
kwargs=dict(timeline_data=item, notify_action='created')).start()
|
||||
|
||||
# Check if any notification agents have notifications enabled
|
||||
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():
|
||||
|
||||
|
@ -275,12 +327,44 @@ def check_server_response():
|
|||
else:
|
||||
if ext_ping_count >= 3:
|
||||
logger.info(u"PlexPy Monitor :: Plex remote access is back up.")
|
||||
# Fire off notifications
|
||||
threading.Thread(target=notification_handler.notify_timeline,
|
||||
kwargs=dict(notify_action='extup')).start()
|
||||
|
||||
# Check if any notification agents have notifications enabled
|
||||
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
|
||||
|
||||
if ext_ping_count == 3:
|
||||
# Fire off notifications
|
||||
threading.Thread(target=notification_handler.notify_timeline,
|
||||
kwargs=dict(notify_action='extdown')).start()
|
||||
# Check if any notification agents have notifications enabled
|
||||
if any(d['on_extdown'] for d in notifiers.available_notification_agents()):
|
||||
# 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.")
|
|
@ -13,7 +13,7 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# 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 plexpy
|
||||
|
@ -78,9 +78,10 @@ class ActivityProcessor(object):
|
|||
result = self.db.upsert('sessions', values, keys)
|
||||
|
||||
if result == 'insert':
|
||||
# Push any notifications - Push it on it's own thread so we don't hold up our db actions
|
||||
if notify:
|
||||
# Check if any notification agents have notifications enabled
|
||||
if notify and any(d['on_play'] for d in notifiers.available_notification_agents()):
|
||||
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,
|
||||
kwargs=dict(stream_data=values, notify_action='play')).start()
|
||||
|
||||
|
@ -97,16 +98,23 @@ class ActivityProcessor(object):
|
|||
ip_address = {'ip_address': ip_address}
|
||||
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):
|
||||
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']
|
||||
|
||||
library_data = libraries.Libraries()
|
||||
library_details = library_data.get_details(section_id=section_id)
|
||||
if not is_import:
|
||||
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:
|
||||
logging_enabled = False
|
||||
|
@ -116,8 +124,14 @@ class ActivityProcessor(object):
|
|||
stopped = int(session['stopped'])
|
||||
else:
|
||||
stopped = int(time.time())
|
||||
elif session['stopped']:
|
||||
stopped = int(session['stopped'])
|
||||
else:
|
||||
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 \
|
||||
session['media_type'] == 'movie':
|
||||
|
@ -137,14 +151,14 @@ class ActivityProcessor(object):
|
|||
else:
|
||||
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 \
|
||||
(real_play_time < int(plexpy.CONFIG.LOGGING_IGNORE_INTERVAL)):
|
||||
logging_enabled = False
|
||||
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." %
|
||||
(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:
|
||||
logging_enabled = False
|
||||
logger.debug(u"PlexPy ActivityProcessor :: Play duration for ratingKey %s is %s secs, "
|
||||
|
@ -156,17 +170,29 @@ class ActivityProcessor(object):
|
|||
logging_enabled = False
|
||||
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." %
|
||||
(session['rating_key'], str(real_play_time),
|
||||
import_ignore_interval))
|
||||
(session['rating_key'], str(real_play_time), 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
|
||||
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
|
||||
logger.debug(u"PlexPy ActivityProcessor :: History logging for library '%s' is disabled." % library_details['section_name'])
|
||||
|
||||
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...")
|
||||
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, ' \
|
||||
|
@ -218,13 +244,22 @@ class ActivityProcessor(object):
|
|||
# % last_id)
|
||||
|
||||
# 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...")
|
||||
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, ' \
|
||||
'video_framerate, aspect_ratio, audio_channels, transcode_protocol, transcode_container, ' \
|
||||
'transcode_video_codec, transcode_audio_codec, transcode_audio_channels, transcode_width, ' \
|
||||
'transcode_height) VALUES ' \
|
||||
'(last_insert_rowid(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
|
||||
'transcode_height, transcode_decision) VALUES ' \
|
||||
'(last_insert_rowid(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
|
||||
|
||||
args = [session['rating_key'], session['video_decision'], session['audio_decision'],
|
||||
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['audio_channels'], session['transcode_protocol'], session['transcode_container'],
|
||||
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...")
|
||||
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
|
||||
directors = ";".join(metadata['directors'])
|
||||
writers = ";".join(metadata['writers'])
|
||||
|
@ -280,6 +308,9 @@ class ActivityProcessor(object):
|
|||
# logger.debug(u"PlexPy ActivityProcessor :: Writing session_history_metadata transaction...")
|
||||
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):
|
||||
|
||||
logger.debug(u"PlexPy ActivityProcessor :: Requesting log lines...")
|
||||
|
@ -351,12 +382,15 @@ class ActivityProcessor(object):
|
|||
|
||||
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():
|
||||
values = {'view_offset': int(view_offset)}
|
||||
if state:
|
||||
values['state'] = state
|
||||
|
||||
for k,v in kwargs.iteritems():
|
||||
values[k] = v
|
||||
|
||||
keys = {'session_key': session_key}
|
||||
result = self.db.upsert('sessions', values, keys)
|
||||
|
||||
|
|
|
@ -62,6 +62,7 @@ MEDIA_FLAGS_VIDEO = {'avc1': 'h264',
|
|||
SCHEDULER_LIST = ['Check GitHub for updates',
|
||||
'Check for active sessions',
|
||||
'Check for recently added items',
|
||||
'Check for Plex updates',
|
||||
'Check for Plex remote access',
|
||||
'Refresh users list',
|
||||
'Refresh libraries list',
|
||||
|
|
|
@ -48,6 +48,7 @@ _CONFIG_DEFINITIONS = {
|
|||
'BOXCAR_ON_INTDOWN': (int, 'Boxcar', 0),
|
||||
'BOXCAR_ON_EXTUP': (int, 'Boxcar', 0),
|
||||
'BOXCAR_ON_INTUP': (int, 'Boxcar', 0),
|
||||
'BOXCAR_ON_PMSUPDATE': (int, 'Boxcar', 0),
|
||||
'BUFFER_THRESHOLD': (int, 'Monitoring', 3),
|
||||
'BUFFER_WAIT': (int, 'Monitoring', 900),
|
||||
'BACKUP_DIR': (str, 'General', ''),
|
||||
|
@ -81,6 +82,7 @@ _CONFIG_DEFINITIONS = {
|
|||
'EMAIL_ON_INTDOWN': (int, 'Email', 0),
|
||||
'EMAIL_ON_EXTUP': (int, 'Email', 0),
|
||||
'EMAIL_ON_INTUP': (int, 'Email', 0),
|
||||
'EMAIL_ON_PMSUPDATE': (int, 'Email', 0),
|
||||
'ENABLE_HTTPS': (int, 'General', 0),
|
||||
'FACEBOOK_ENABLED': (int, 'Facebook', 0),
|
||||
'FACEBOOK_REDIRECT_URI': (str, 'Facebook', ''),
|
||||
|
@ -88,6 +90,7 @@ _CONFIG_DEFINITIONS = {
|
|||
'FACEBOOK_APP_SECRET': (str, 'Facebook', ''),
|
||||
'FACEBOOK_TOKEN': (str, 'Facebook', ''),
|
||||
'FACEBOOK_GROUP': (str, 'Facebook', ''),
|
||||
'FACEBOOK_INCL_PMSLINK': (int, 'Facebook', 0),
|
||||
'FACEBOOK_INCL_POSTER': (int, 'Facebook', 1),
|
||||
'FACEBOOK_INCL_SUBJECT': (int, 'Facebook', 1),
|
||||
'FACEBOOK_ON_PLAY': (int, 'Facebook', 0),
|
||||
|
@ -101,6 +104,7 @@ _CONFIG_DEFINITIONS = {
|
|||
'FACEBOOK_ON_INTDOWN': (int, 'Facebook', 0),
|
||||
'FACEBOOK_ON_EXTUP': (int, 'Facebook', 0),
|
||||
'FACEBOOK_ON_INTUP': (int, 'Facebook', 0),
|
||||
'FACEBOOK_ON_PMSUPDATE': (int, 'Facebook', 0),
|
||||
'FIRST_RUN_COMPLETE': (int, 'General', 0),
|
||||
'FREEZE_DB': (int, 'General', 0),
|
||||
'GET_FILE_SIZES': (int, 'General', 0),
|
||||
|
@ -126,6 +130,7 @@ _CONFIG_DEFINITIONS = {
|
|||
'GROWL_ON_INTDOWN': (int, 'Growl', 0),
|
||||
'GROWL_ON_EXTUP': (int, 'Growl', 0),
|
||||
'GROWL_ON_INTUP': (int, 'Growl', 0),
|
||||
'GROWL_ON_PMSUPDATE': (int, 'Growl', 0),
|
||||
'HOME_LIBRARY_CARDS': (list, 'General', ['first_run']),
|
||||
'HOME_STATS_LENGTH': (int, 'General', 30),
|
||||
'HOME_STATS_TYPE': (int, 'General', 0),
|
||||
|
@ -159,6 +164,7 @@ _CONFIG_DEFINITIONS = {
|
|||
'IFTTT_ON_INTDOWN': (int, 'IFTTT', 0),
|
||||
'IFTTT_ON_EXTUP': (int, 'IFTTT', 0),
|
||||
'IFTTT_ON_INTUP': (int, 'IFTTT', 0),
|
||||
'IFTTT_ON_PMSUPDATE': (int, 'IFTTT', 0),
|
||||
'JOURNAL_MODE': (str, 'Advanced', 'wal'),
|
||||
'LAUNCH_BROWSER': (int, 'General', 1),
|
||||
'LOG_DIR': (str, 'General', ''),
|
||||
|
@ -173,6 +179,7 @@ _CONFIG_DEFINITIONS = {
|
|||
'MUSIC_NOTIFY_ON_START': (int, 'Monitoring', 1),
|
||||
'MUSIC_NOTIFY_ON_STOP': (int, 'Monitoring', 0),
|
||||
'MUSIC_NOTIFY_ON_PAUSE': (int, 'Monitoring', 0),
|
||||
'MONITOR_PMS_UPDATES': (int, 'Monitoring', 0),
|
||||
'MONITOR_REMOTE_ACCESS': (int, 'Monitoring', 0),
|
||||
'MONITORING_INTERVAL': (int, 'Monitoring', 60),
|
||||
'MONITORING_USE_WEBSOCKET': (int, 'Monitoring', 0),
|
||||
|
@ -190,6 +197,7 @@ _CONFIG_DEFINITIONS = {
|
|||
'NMA_ON_INTDOWN': (int, 'NMA', 0),
|
||||
'NMA_ON_EXTUP': (int, 'NMA', 0),
|
||||
'NMA_ON_INTUP': (int, 'NMA', 0),
|
||||
'NMA_ON_PMSUPDATE': (int, 'NMA', 0),
|
||||
'NOTIFY_CONSECUTIVE': (int, 'Monitoring', 1),
|
||||
'NOTIFY_UPLOAD_POSTERS': (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_INTUP_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'),
|
||||
'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', ''),
|
||||
'OSX_NOTIFY_APP': (str, 'OSX_Notify', '/Applications/PlexPy'),
|
||||
'OSX_NOTIFY_ENABLED': (int, 'OSX_Notify', 0),
|
||||
|
@ -232,6 +242,7 @@ _CONFIG_DEFINITIONS = {
|
|||
'OSX_NOTIFY_ON_INTDOWN': (int, 'OSX_Notify', 0),
|
||||
'OSX_NOTIFY_ON_EXTUP': (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_ENABLED': (int, 'Plex', 0),
|
||||
'PLEX_PASSWORD': (str, 'Plex', ''),
|
||||
|
@ -247,6 +258,7 @@ _CONFIG_DEFINITIONS = {
|
|||
'PLEX_ON_INTDOWN': (int, 'Plex', 0),
|
||||
'PLEX_ON_EXTUP': (int, 'Plex', 0),
|
||||
'PLEX_ON_INTUP': (int, 'Plex', 0),
|
||||
'PLEX_ON_PMSUPDATE': (int, 'Plex', 0),
|
||||
'PROWL_ENABLED': (int, 'Prowl', 0),
|
||||
'PROWL_KEYS': (str, 'Prowl', ''),
|
||||
'PROWL_PRIORITY': (int, 'Prowl', 0),
|
||||
|
@ -261,6 +273,7 @@ _CONFIG_DEFINITIONS = {
|
|||
'PROWL_ON_INTDOWN': (int, 'Prowl', 0),
|
||||
'PROWL_ON_EXTUP': (int, 'Prowl', 0),
|
||||
'PROWL_ON_INTUP': (int, 'Prowl', 0),
|
||||
'PROWL_ON_PMSUPDATE': (int, 'Prowl', 0),
|
||||
'PUSHALOT_APIKEY': (str, 'Pushalot', ''),
|
||||
'PUSHALOT_ENABLED': (int, 'Pushalot', 0),
|
||||
'PUSHALOT_ON_PLAY': (int, 'Pushalot', 0),
|
||||
|
@ -274,6 +287,7 @@ _CONFIG_DEFINITIONS = {
|
|||
'PUSHALOT_ON_INTDOWN': (int, 'Pushalot', 0),
|
||||
'PUSHALOT_ON_EXTUP': (int, 'Pushalot', 0),
|
||||
'PUSHALOT_ON_INTUP': (int, 'Pushalot', 0),
|
||||
'PUSHALOT_ON_PMSUPDATE': (int, 'Pushalot', 0),
|
||||
'PUSHBULLET_APIKEY': (str, 'PushBullet', ''),
|
||||
'PUSHBULLET_DEVICEID': (str, 'PushBullet', ''),
|
||||
'PUSHBULLET_CHANNEL_TAG': (str, 'PushBullet', ''),
|
||||
|
@ -289,6 +303,7 @@ _CONFIG_DEFINITIONS = {
|
|||
'PUSHBULLET_ON_INTDOWN': (int, 'PushBullet', 0),
|
||||
'PUSHBULLET_ON_EXTUP': (int, 'PushBullet', 0),
|
||||
'PUSHBULLET_ON_INTUP': (int, 'PushBullet', 0),
|
||||
'PUSHBULLET_ON_PMSUPDATE': (int, 'PushBullet', 0),
|
||||
'PUSHOVER_APITOKEN': (str, 'Pushover', ''),
|
||||
'PUSHOVER_ENABLED': (int, 'Pushover', 0),
|
||||
'PUSHOVER_HTML_SUPPORT': (int, 'Pushover', 1),
|
||||
|
@ -306,6 +321,7 @@ _CONFIG_DEFINITIONS = {
|
|||
'PUSHOVER_ON_INTDOWN': (int, 'Pushover', 0),
|
||||
'PUSHOVER_ON_EXTUP': (int, 'Pushover', 0),
|
||||
'PUSHOVER_ON_INTUP': (int, 'Pushover', 0),
|
||||
'PUSHOVER_ON_PMSUPDATE': (int, 'Pushover', 0),
|
||||
'REFRESH_LIBRARIES_INTERVAL': (int, 'Monitoring', 12),
|
||||
'REFRESH_LIBRARIES_ON_STARTUP': (int, 'Monitoring', 1),
|
||||
'REFRESH_USERS_INTERVAL': (int, 'Monitoring', 12),
|
||||
|
@ -327,6 +343,7 @@ _CONFIG_DEFINITIONS = {
|
|||
'SLACK_ON_INTDOWN': (int, 'Slack', 0),
|
||||
'SLACK_ON_EXTUP': (int, 'Slack', 0),
|
||||
'SLACK_ON_INTUP': (int, 'Slack', 0),
|
||||
'SLACK_ON_PMSUPDATE': (int, 'Slack', 0),
|
||||
'SCRIPTS_ENABLED': (int, 'Scripts', 0),
|
||||
'SCRIPTS_FOLDER': (unicode, 'Scripts', ''),
|
||||
'SCRIPTS_ON_PLAY': (int, 'Scripts', 0),
|
||||
|
@ -340,6 +357,7 @@ _CONFIG_DEFINITIONS = {
|
|||
'SCRIPTS_ON_EXTUP': (int, 'Scripts', 0),
|
||||
'SCRIPTS_ON_INTDOWN': (int, 'Scripts', 0),
|
||||
'SCRIPTS_ON_INTUP': (int, 'Scripts', 0),
|
||||
'SCRIPTS_ON_PMSUPDATE': (int, 'Scripts', 0),
|
||||
'SCRIPTS_ON_PLAY_SCRIPT': (unicode, 'Scripts', ''),
|
||||
'SCRIPTS_ON_STOP_SCRIPT': (unicode, 'Scripts', ''),
|
||||
'SCRIPTS_ON_PAUSE_SCRIPT': (unicode, 'Scripts', ''),
|
||||
|
@ -351,6 +369,7 @@ _CONFIG_DEFINITIONS = {
|
|||
'SCRIPTS_ON_EXTUP_SCRIPT': (unicode, 'Scripts', ''),
|
||||
'SCRIPTS_ON_INTDOWN_SCRIPT': (unicode, 'Scripts', ''),
|
||||
'SCRIPTS_ON_INTUP_SCRIPT': (unicode, 'Scripts', ''),
|
||||
'SCRIPTS_ON_PMSUPDATE_SCRIPT': (unicode, 'Scripts', ''),
|
||||
'TELEGRAM_BOT_TOKEN': (str, 'Telegram', ''),
|
||||
'TELEGRAM_ENABLED': (int, 'Telegram', 0),
|
||||
'TELEGRAM_CHAT_ID': (str, 'Telegram', ''),
|
||||
|
@ -366,6 +385,7 @@ _CONFIG_DEFINITIONS = {
|
|||
'TELEGRAM_ON_INTDOWN': (int, 'Telegram', 0),
|
||||
'TELEGRAM_ON_EXTUP': (int, 'Telegram', 0),
|
||||
'TELEGRAM_ON_INTUP': (int, 'Telegram', 0),
|
||||
'TELEGRAM_ON_PMSUPDATE': (int, 'Telegram', 0),
|
||||
'TV_LOGGING_ENABLE': (int, 'Monitoring', 1),
|
||||
'TV_NOTIFY_ENABLE': (int, 'Monitoring', 0),
|
||||
'TV_NOTIFY_ON_START': (int, 'Monitoring', 1),
|
||||
|
@ -388,6 +408,7 @@ _CONFIG_DEFINITIONS = {
|
|||
'TWITTER_ON_INTDOWN': (int, 'Twitter', 0),
|
||||
'TWITTER_ON_EXTUP': (int, 'Twitter', 0),
|
||||
'TWITTER_ON_INTUP': (int, 'Twitter', 0),
|
||||
'TWITTER_ON_PMSUPDATE': (int, 'Twitter', 0),
|
||||
'UPDATE_DB_INTERVAL': (int, 'General', 24),
|
||||
'UPDATE_SECTION_IDS': (int, 'General', 1),
|
||||
'VERIFY_SSL_CERT': (bool_int, 'Advanced', 1),
|
||||
|
@ -406,7 +427,8 @@ _CONFIG_DEFINITIONS = {
|
|||
'XBMC_ON_EXTDOWN': (int, 'XBMC', 0),
|
||||
'XBMC_ON_INTDOWN': (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)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import os
|
|||
import sqlite3
|
||||
import shutil
|
||||
import threading
|
||||
import time
|
||||
|
||||
import logger
|
||||
import plexpy
|
||||
|
@ -37,9 +38,21 @@ def clear_history_tables():
|
|||
monitor_db.action('DELETE FROM session_history')
|
||||
monitor_db.action('DELETE FROM session_history_media_info')
|
||||
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"):
|
||||
""" Returns the filepath to the db """
|
||||
|
||||
|
|
|
@ -58,11 +58,10 @@ class DataFactory(object):
|
|||
'session_history_metadata.thumb',
|
||||
'session_history_metadata.parent_thumb',
|
||||
'session_history_metadata.grandparent_thumb',
|
||||
'MAX((CASE WHEN view_offset IS NULL THEN 0.1 ELSE view_offset * 1.0 END) / \
|
||||
(CASE WHEN session_history_metadata.duration IS NULL THEN 1.0 \
|
||||
ELSE session_history_metadata.duration * 1.0 END) * 100) AS percent_complete',
|
||||
'session_history_media_info.video_decision',
|
||||
'session_history_media_info.audio_decision',
|
||||
'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 OR session_history_metadata.duration = "") \
|
||||
THEN 1.0 ELSE session_history_metadata.duration * 1.0 END) * 100) AS percent_complete',
|
||||
'session_history_media_info.transcode_decision',
|
||||
'COUNT(*) AS group_count',
|
||||
'GROUP_CONCAT(session_history.id) AS group_ids'
|
||||
]
|
||||
|
@ -138,8 +137,7 @@ class DataFactory(object):
|
|||
'media_index': item['media_index'],
|
||||
'parent_media_index': item['parent_media_index'],
|
||||
'thumb': thumb,
|
||||
'video_decision': item['video_decision'],
|
||||
'audio_decision': item['audio_decision'],
|
||||
'transcode_decision': item['transcode_decision'],
|
||||
'percent_complete': int(round(item['percent_complete'])),
|
||||
'watched_status': watched_status,
|
||||
'group_count': item['group_count'],
|
||||
|
@ -626,24 +624,21 @@ class DataFactory(object):
|
|||
|
||||
title = 'Concurrent Transcodes'
|
||||
query = base_query \
|
||||
+ 'AND (session_history_media_info.video_decision = "transcode" ' \
|
||||
'OR session_history_media_info.audio_decision = "transcode") '
|
||||
+ 'AND session_history_media_info.transcode_decision = "transcode" '
|
||||
result = monitor_db.select(query)
|
||||
if result:
|
||||
most_concurrent.append(calc_most_concurrent(title, result))
|
||||
|
||||
title = 'Concurrent Direct Streams'
|
||||
query = base_query \
|
||||
+ 'AND (session_history_media_info.video_decision != "transcode" ' \
|
||||
'AND session_history_media_info.audio_decision = "copy") '
|
||||
+ 'AND session_history_media_info.transcode_decision = "copy" '
|
||||
result = monitor_db.select(query)
|
||||
if result:
|
||||
most_concurrent.append(calc_most_concurrent(title, result))
|
||||
|
||||
title = 'Concurrent Direct Plays'
|
||||
query = base_query \
|
||||
+ 'AND (session_history_media_info.video_decision = "direct play" ' \
|
||||
'OR session_history_media_info.audio_decision = "direct play") '
|
||||
+ 'AND session_history_media_info.transcode_decision = "direct play" '
|
||||
result = monitor_db.select(query)
|
||||
if 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 ' \
|
||||
'FROM session_history ' \
|
||||
'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
|
||||
result = monitor_db.select(query)
|
||||
except Exception as e:
|
||||
|
@ -860,16 +856,28 @@ class DataFactory(object):
|
|||
|
||||
return ip_address
|
||||
|
||||
def get_poster_url(self, rating_key=''):
|
||||
def get_poster_url(self, rating_key='', metadata=None):
|
||||
monitor_db = database.MonitorDatabase()
|
||||
|
||||
poster_url = ''
|
||||
poster_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:
|
||||
query = 'SELECT id, poster_url FROM notify_log ' \
|
||||
'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)
|
||||
except Exception as 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
|
||||
|
||||
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=''):
|
||||
monitor_db = database.MonitorDatabase()
|
||||
|
||||
|
@ -1188,4 +1206,16 @@ class DataFactory(object):
|
|||
'draw': query['draw']
|
||||
}
|
||||
|
||||
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.'
|
111
plexpy/graphs.py
111
plexpy/graphs.py
|
@ -490,40 +490,39 @@ class Graphs(object):
|
|||
try:
|
||||
if y_axis == 'plays':
|
||||
query = 'SELECT date(session_history.started, "unixepoch", "localtime") AS date_played, ' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
|
||||
'OR session_history_media_info.audio_decision = "direct play") THEN 1 ELSE 0 END) AS dp_count, ' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
|
||||
'AND session_history_media_info.audio_decision = "copy") THEN 1 ELSE 0 END) AS ds_count, ' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \
|
||||
'OR session_history_media_info.audio_decision = "transcode") THEN 1 ELSE 0 END) AS tc_count ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
|
||||
'THEN 1 ELSE 0 END) AS dp_count, ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
|
||||
'THEN 1 ELSE 0 END) AS ds_count, ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
|
||||
'THEN 1 ELSE 0 END) AS tc_count ' \
|
||||
'FROM session_history ' \
|
||||
'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \
|
||||
'WHERE (datetime(session_history.stopped, "unixepoch", "localtime") >= ' \
|
||||
'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 ' \
|
||||
'ORDER BY started ASC' % time_range
|
||||
|
||||
result = monitor_db.select(query)
|
||||
else:
|
||||
query = 'SELECT date(session_history.started, "unixepoch", "localtime") AS date_played, ' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
|
||||
'OR session_history_media_info.audio_decision = "direct play") ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
|
||||
'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, ' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
|
||||
'AND session_history_media_info.audio_decision = "copy") ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
|
||||
'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, ' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \
|
||||
'OR session_history_media_info.audio_decision = "transcode") ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
|
||||
'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 ' \
|
||||
'FROM session_history ' \
|
||||
'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \
|
||||
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") >= ' \
|
||||
'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 ' \
|
||||
'ORDER BY started ASC' % time_range
|
||||
|
||||
|
@ -583,12 +582,12 @@ class Graphs(object):
|
|||
try:
|
||||
if y_axis == 'plays':
|
||||
query = 'SELECT session_history_media_info.video_resolution AS resolution, ' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
|
||||
'OR session_history_media_info.audio_decision = "direct play") THEN 1 ELSE 0 END) AS dp_count, ' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
|
||||
'AND session_history_media_info.audio_decision = "copy") THEN 1 ELSE 0 END) AS ds_count, ' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \
|
||||
'OR session_history_media_info.audio_decision = "transcode") THEN 1 ELSE 0 END) AS tc_count, ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
|
||||
'THEN 1 ELSE 0 END) AS dp_count, ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
|
||||
'THEN 1 ELSE 0 END) AS ds_count, ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
|
||||
'THEN 1 ELSE 0 END) AS tc_count, ' \
|
||||
'COUNT(session_history.id) AS total_count ' \
|
||||
'FROM session_history ' \
|
||||
'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)
|
||||
else:
|
||||
query = 'SELECT session_history_media_info.video_resolution AS resolution,' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
|
||||
'OR session_history_media_info.audio_decision = "direct play") ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
|
||||
'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, ' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
|
||||
'AND session_history_media_info.audio_decision = "copy") ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
|
||||
'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, ' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \
|
||||
'OR session_history_media_info.audio_decision = "transcode") ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
|
||||
'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, ' \
|
||||
'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 <= 2160 THEN "4K" ' \
|
||||
'ELSE "unknown" END) ELSE session_history_media_info.video_resolution END) AS resolution, ' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
|
||||
'OR session_history_media_info.audio_decision = "direct play") THEN 1 ELSE 0 END) AS dp_count, ' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
|
||||
'AND session_history_media_info.audio_decision = "copy") THEN 1 ELSE 0 END) AS ds_count, ' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" '\
|
||||
'OR session_history_media_info.audio_decision = "transcode") THEN 1 ELSE 0 END) AS tc_count, ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
|
||||
'THEN 1 ELSE 0 END) AS dp_count, ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
|
||||
'THEN 1 ELSE 0 END) AS ds_count, ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" '\
|
||||
'THEN 1 ELSE 0 END) AS tc_count, ' \
|
||||
'COUNT(session_history.id) AS total_count ' \
|
||||
'FROM session_history ' \
|
||||
'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 <= 2160 THEN "4K" ' \
|
||||
'ELSE "unknown" END) ELSE session_history_media_info.video_resolution END) AS resolution, ' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
|
||||
'OR session_history_media_info.audio_decision = "direct play") ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
|
||||
'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, ' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
|
||||
'AND session_history_media_info.audio_decision = "copy") ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
|
||||
'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, ' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \
|
||||
'OR session_history_media_info.audio_decision = "transcode") ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
|
||||
'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, ' \
|
||||
'SUM(CASE WHEN stopped > 0 THEN (stopped - started) ' \
|
||||
|
@ -759,12 +752,12 @@ class Graphs(object):
|
|||
try:
|
||||
if y_axis == 'plays':
|
||||
query = 'SELECT session_history.platform AS platform, ' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
|
||||
'OR session_history_media_info.audio_decision = "direct play") THEN 1 ELSE 0 END) AS dp_count, ' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
|
||||
'AND session_history_media_info.audio_decision = "copy") THEN 1 ELSE 0 END) AS ds_count, ' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \
|
||||
'OR session_history_media_info.audio_decision = "transcode") THEN 1 ELSE 0 END) AS tc_count, ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
|
||||
'THEN 1 ELSE 0 END) AS dp_count, ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
|
||||
'THEN 1 ELSE 0 END) AS ds_count, ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
|
||||
'THEN 1 ELSE 0 END) AS tc_count, ' \
|
||||
'COUNT(session_history.id) AS total_count ' \
|
||||
'FROM session_history ' \
|
||||
'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)
|
||||
else:
|
||||
query = 'SELECT session_history.platform AS platform, ' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
|
||||
'OR session_history_media_info.audio_decision = "direct play") ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
|
||||
'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, ' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
|
||||
'AND session_history_media_info.audio_decision = "copy") ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
|
||||
'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, ' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \
|
||||
'AND session_history_media_info.audio_decision = "transcode") ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
|
||||
'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, ' \
|
||||
'SUM(CASE WHEN session_history.stopped > 0 ' \
|
||||
|
@ -838,12 +828,12 @@ class Graphs(object):
|
|||
if y_axis == 'plays':
|
||||
query = 'SELECT ' \
|
||||
'(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" ' \
|
||||
'OR session_history_media_info.audio_decision = "direct play") THEN 1 ELSE 0 END) AS dp_count, ' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
|
||||
'AND session_history_media_info.audio_decision = "copy") THEN 1 ELSE 0 END) AS ds_count, ' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \
|
||||
'OR session_history_media_info.audio_decision = "transcode") THEN 1 ELSE 0 END) AS tc_count, ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
|
||||
'THEN 1 ELSE 0 END) AS dp_count, ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
|
||||
'THEN 1 ELSE 0 END) AS ds_count, ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
|
||||
'THEN 1 ELSE 0 END) AS tc_count, ' \
|
||||
'COUNT(session_history.id) AS total_count ' \
|
||||
'FROM session_history ' \
|
||||
'JOIN users ON session_history.user_id = users.user_id ' \
|
||||
|
@ -858,16 +848,13 @@ class Graphs(object):
|
|||
else:
|
||||
query = 'SELECT ' \
|
||||
'(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" ' \
|
||||
'OR session_history_media_info.audio_decision = "direct play") ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
|
||||
'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, ' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
|
||||
'AND session_history_media_info.audio_decision = "copy") ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
|
||||
'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, ' \
|
||||
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \
|
||||
'AND session_history_media_info.audio_decision = "transcode") ' \
|
||||
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
|
||||
'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, ' \
|
||||
'SUM(CASE WHEN session_history.stopped > 0 ' \
|
||||
|
|
|
@ -148,7 +148,6 @@ class Libraries(object):
|
|||
'session_history_metadata.year',
|
||||
'session_history_metadata.media_index',
|
||||
'session_history_metadata.parent_media_index',
|
||||
'session_history_media_info.video_decision',
|
||||
'library_sections.do_notify',
|
||||
'library_sections.do_notify_created',
|
||||
'library_sections.keep_history'
|
||||
|
@ -540,7 +539,7 @@ class Libraries(object):
|
|||
def get_details(self, section_id=None):
|
||||
from plexpy import pmsconnect
|
||||
|
||||
default_return = {'section_id': None,
|
||||
default_return = {'section_id': 0,
|
||||
'section_name': 'Local',
|
||||
'section_type': '',
|
||||
'library_thumb': common.DEFAULT_COVER_THUMB,
|
||||
|
@ -550,7 +549,7 @@ class Libraries(object):
|
|||
'child_count': 0,
|
||||
'do_notify': 0,
|
||||
'do_notify_created': 0,
|
||||
'keep_history': 0
|
||||
'keep_history': 1
|
||||
}
|
||||
|
||||
if not section_id:
|
||||
|
@ -603,7 +602,8 @@ class Libraries(object):
|
|||
return library_details
|
||||
|
||||
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
|
||||
pmsconnect.refresh_libraries()
|
||||
|
||||
|
@ -613,9 +613,9 @@ class Libraries(object):
|
|||
return library_details
|
||||
|
||||
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
|
||||
# Use "Local" library to retain compatibility with PlexWatch database value
|
||||
return default_return
|
||||
|
||||
def get_watch_time_stats(self, section_id=None):
|
||||
|
|
|
@ -50,7 +50,10 @@ def notify(stream_data=None, notify_action=None):
|
|||
for agent in notifiers.available_notification_agents():
|
||||
if agent['on_play'] and notify_action == 'play':
|
||||
# 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'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1],
|
||||
|
@ -68,7 +71,10 @@ def notify(stream_data=None, notify_action=None):
|
|||
elif agent['on_stop'] and notify_action == 'stop' \
|
||||
and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < plexpy.CONFIG.NOTIFY_WATCHED_PERCENT):
|
||||
# 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'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1],
|
||||
|
@ -86,7 +92,10 @@ def notify(stream_data=None, notify_action=None):
|
|||
elif agent['on_pause'] and notify_action == 'pause' \
|
||||
and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < 99):
|
||||
# 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'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1],
|
||||
|
@ -104,7 +113,10 @@ def notify(stream_data=None, notify_action=None):
|
|||
elif agent['on_resume'] and notify_action == 'resume' \
|
||||
and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < 99):
|
||||
# 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'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1],
|
||||
|
@ -121,7 +133,10 @@ def notify(stream_data=None, notify_action=None):
|
|||
|
||||
elif agent['on_buffer'] and notify_action == 'buffer':
|
||||
# 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'],
|
||||
subject=notify_strings[0],
|
||||
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 not any(d['agent_id'] == agent['id'] and d['notify_action'] == notify_action for d in notify_states):
|
||||
# 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'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1],
|
||||
|
@ -163,7 +181,10 @@ def notify(stream_data=None, notify_action=None):
|
|||
for agent in notifiers.available_notification_agents():
|
||||
if agent['on_play'] and notify_action == 'play':
|
||||
# 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'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1],
|
||||
|
@ -180,7 +201,10 @@ def notify(stream_data=None, notify_action=None):
|
|||
|
||||
elif agent['on_stop'] and notify_action == 'stop':
|
||||
# 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'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1],
|
||||
|
@ -197,7 +221,10 @@ def notify(stream_data=None, notify_action=None):
|
|||
|
||||
elif agent['on_pause'] and notify_action == 'pause':
|
||||
# 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'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1],
|
||||
|
@ -214,7 +241,10 @@ def notify(stream_data=None, notify_action=None):
|
|||
|
||||
elif agent['on_resume'] and notify_action == 'resume':
|
||||
# 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'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1],
|
||||
|
@ -231,7 +261,10 @@ def notify(stream_data=None, notify_action=None):
|
|||
|
||||
elif agent['on_buffer'] and notify_action == 'buffer':
|
||||
# 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'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1],
|
||||
|
@ -260,7 +293,10 @@ def notify_timeline(timeline_data=None, notify_action=None):
|
|||
for agent in notifiers.available_notification_agents():
|
||||
if agent['on_created'] and notify_action == 'created':
|
||||
# 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'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1],
|
||||
|
@ -279,7 +315,9 @@ def notify_timeline(timeline_data=None, notify_action=None):
|
|||
for agent in notifiers.available_notification_agents():
|
||||
if agent['on_extdown'] and notify_action == 'extdown':
|
||||
# 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],
|
||||
|
@ -295,7 +333,9 @@ def notify_timeline(timeline_data=None, notify_action=None):
|
|||
|
||||
if agent['on_intdown'] and notify_action == 'intdown':
|
||||
# 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],
|
||||
|
@ -311,7 +351,9 @@ def notify_timeline(timeline_data=None, notify_action=None):
|
|||
|
||||
if agent['on_extup'] and notify_action == 'extup':
|
||||
# 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],
|
||||
|
@ -327,7 +369,27 @@ def notify_timeline(timeline_data=None, notify_action=None):
|
|||
|
||||
if agent['on_intup'] and notify_action == 'intup':
|
||||
# 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'],
|
||||
subject=notify_strings[0],
|
||||
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.")
|
||||
|
||||
|
||||
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
|
||||
date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','').replace('zz','')
|
||||
time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','')
|
||||
duration_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','').replace('a','').replace('A','')
|
||||
date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','')
|
||||
time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','')
|
||||
duration_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('a','').replace('A','')
|
||||
|
||||
# Get the server 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()
|
||||
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:
|
||||
updated_at = server_times[0]['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
|
||||
if metadata['media_type'] == 'movie':
|
||||
# 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':
|
||||
# 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':
|
||||
# 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:
|
||||
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' \
|
||||
and pattern:
|
||||
# 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_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT))
|
||||
on_stop_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT))
|
||||
on_stop_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT))
|
||||
on_pause_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT))
|
||||
on_pause_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT))
|
||||
on_resume_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT))
|
||||
on_resume_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT))
|
||||
on_buffer_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT))
|
||||
on_buffer_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT))
|
||||
on_watched_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT))
|
||||
on_watched_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT))
|
||||
on_created_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT))
|
||||
on_created_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT))
|
||||
script_args_text = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_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), agent_id)
|
||||
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), agent_id)
|
||||
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), agent_id)
|
||||
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), agent_id)
|
||||
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), agent_id)
|
||||
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), agent_id)
|
||||
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), agent_id)
|
||||
script_args_text = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT), agent_id)
|
||||
else:
|
||||
on_start_subject = plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT
|
||||
on_start_body = plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT
|
||||
on_stop_subject = plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT
|
||||
on_stop_body = plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT
|
||||
on_pause_subject = plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT
|
||||
on_pause_body = plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT
|
||||
on_resume_subject = plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT
|
||||
on_resume_body = plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT
|
||||
on_buffer_subject = plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT
|
||||
on_buffer_body = plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT
|
||||
on_watched_subject = plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT
|
||||
on_watched_body = plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT
|
||||
on_created_subject = plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT
|
||||
on_created_body = plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT
|
||||
script_args_text = plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT
|
||||
on_start_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT, agent_id)
|
||||
on_start_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT, agent_id)
|
||||
on_stop_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT, agent_id)
|
||||
on_stop_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT, agent_id)
|
||||
on_pause_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT, agent_id)
|
||||
on_pause_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT, agent_id)
|
||||
on_resume_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT, agent_id)
|
||||
on_resume_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT, agent_id)
|
||||
on_buffer_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT, agent_id)
|
||||
on_buffer_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT, agent_id)
|
||||
on_watched_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT, agent_id)
|
||||
on_watched_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT, agent_id)
|
||||
on_created_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT, agent_id)
|
||||
on_created_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT, agent_id)
|
||||
script_args_text = strip_tag(plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT, agent_id)
|
||||
|
||||
# Create a title
|
||||
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
|
||||
'server_name': server_name,
|
||||
'server_uptime': server_uptime,
|
||||
'server_version': server_identity.get('version',''),
|
||||
'action': notify_action.title(),
|
||||
'datestamp': arrow.now().format(date_format),
|
||||
'timestamp': arrow.now().format(time_format),
|
||||
|
@ -854,10 +921,10 @@ def build_notify_text(session=None, timeline=None, notify_action=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
|
||||
date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','').replace('zz','')
|
||||
time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','')
|
||||
date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','')
|
||||
time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','')
|
||||
|
||||
# Get the server name
|
||||
server_name = plexpy.CONFIG.PMS_NAME
|
||||
|
@ -866,6 +933,14 @@ def build_server_notify_text(notify_action=None):
|
|||
plex_tv = plextv.PlexTV()
|
||||
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:
|
||||
updated_at = server_times[0]['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)
|
||||
|
||||
on_extdown_subject = plexpy.CONFIG.NOTIFY_ON_EXTDOWN_SUBJECT_TEXT
|
||||
on_extdown_body = plexpy.CONFIG.NOTIFY_ON_EXTDOWN_BODY_TEXT
|
||||
on_intdown_subject = plexpy.CONFIG.NOTIFY_ON_INTDOWN_SUBJECT_TEXT
|
||||
on_intdown_body = plexpy.CONFIG.NOTIFY_ON_INTDOWN_BODY_TEXT
|
||||
on_extup_subject = plexpy.CONFIG.NOTIFY_ON_EXTUP_SUBJECT_TEXT
|
||||
on_extup_body = plexpy.CONFIG.NOTIFY_ON_EXTUP_BODY_TEXT
|
||||
on_intup_subject = plexpy.CONFIG.NOTIFY_ON_INTUP_SUBJECT_TEXT
|
||||
on_intup_body = plexpy.CONFIG.NOTIFY_ON_INTUP_BODY_TEXT
|
||||
script_args_text = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT))
|
||||
on_extdown_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_EXTDOWN_SUBJECT_TEXT, agent_id)
|
||||
on_extdown_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_EXTDOWN_BODY_TEXT, agent_id)
|
||||
on_intdown_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_INTDOWN_SUBJECT_TEXT, agent_id)
|
||||
on_intdown_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_INTDOWN_BODY_TEXT, agent_id)
|
||||
on_extup_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_EXTUP_SUBJECT_TEXT, agent_id)
|
||||
on_extup_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_EXTUP_BODY_TEXT, agent_id)
|
||||
on_intup_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_INTUP_SUBJECT_TEXT, agent_id)
|
||||
on_intup_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_INTUP_BODY_TEXT, agent_id)
|
||||
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
|
||||
'server_name': server_name,
|
||||
'server_uptime': server_uptime,
|
||||
'server_version': server_identity.get('version',''),
|
||||
'action': notify_action.title(),
|
||||
'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
|
||||
subject_text = 'PlexPy (%s)' % server_name
|
||||
|
@ -996,10 +1078,39 @@ def build_server_notify_text(notify_action=None):
|
|||
else:
|
||||
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:
|
||||
return None
|
||||
|
||||
|
||||
def strip_tag(data):
|
||||
p = re.compile(r'<.*?>')
|
||||
def strip_tag(data, agent_id=None):
|
||||
# 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)
|
|
@ -75,7 +75,8 @@ def available_notification_agents():
|
|||
'on_extdown': plexpy.CONFIG.GROWL_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.GROWL_ON_INTDOWN,
|
||||
'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',
|
||||
'id': AGENT_IDS['Prowl'],
|
||||
|
@ -92,7 +93,8 @@ def available_notification_agents():
|
|||
'on_extdown': plexpy.CONFIG.PROWL_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.PROWL_ON_INTDOWN,
|
||||
'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',
|
||||
'id': AGENT_IDS['XBMC'],
|
||||
|
@ -109,7 +111,8 @@ def available_notification_agents():
|
|||
'on_extdown': plexpy.CONFIG.XBMC_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.XBMC_ON_INTDOWN,
|
||||
'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',
|
||||
'id': AGENT_IDS['Plex'],
|
||||
|
@ -126,7 +129,8 @@ def available_notification_agents():
|
|||
'on_extdown': plexpy.CONFIG.PLEX_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.PLEX_ON_INTDOWN,
|
||||
'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',
|
||||
'id': AGENT_IDS['NMA'],
|
||||
|
@ -143,7 +147,8 @@ def available_notification_agents():
|
|||
'on_extdown': plexpy.CONFIG.NMA_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.NMA_ON_INTDOWN,
|
||||
'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',
|
||||
'id': AGENT_IDS['Pushalot'],
|
||||
|
@ -160,7 +165,8 @@ def available_notification_agents():
|
|||
'on_extdown': plexpy.CONFIG.PUSHALOT_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.PUSHALOT_ON_INTDOWN,
|
||||
'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',
|
||||
'id': AGENT_IDS['Pushbullet'],
|
||||
|
@ -177,7 +183,8 @@ def available_notification_agents():
|
|||
'on_extdown': plexpy.CONFIG.PUSHBULLET_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.PUSHBULLET_ON_INTDOWN,
|
||||
'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',
|
||||
'id': AGENT_IDS['Pushover'],
|
||||
|
@ -194,7 +201,8 @@ def available_notification_agents():
|
|||
'on_extdown': plexpy.CONFIG.PUSHOVER_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.PUSHOVER_ON_INTDOWN,
|
||||
'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',
|
||||
'id': AGENT_IDS['Boxcar2'],
|
||||
|
@ -211,7 +219,8 @@ def available_notification_agents():
|
|||
'on_extdown': plexpy.CONFIG.BOXCAR_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.BOXCAR_ON_INTDOWN,
|
||||
'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',
|
||||
'id': AGENT_IDS['Email'],
|
||||
|
@ -228,7 +237,8 @@ def available_notification_agents():
|
|||
'on_extdown': plexpy.CONFIG.EMAIL_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.EMAIL_ON_INTDOWN,
|
||||
'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',
|
||||
'id': AGENT_IDS['Twitter'],
|
||||
|
@ -245,7 +255,8 @@ def available_notification_agents():
|
|||
'on_extdown': plexpy.CONFIG.TWITTER_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.TWITTER_ON_INTDOWN,
|
||||
'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',
|
||||
'id': AGENT_IDS['IFTTT'],
|
||||
|
@ -262,7 +273,8 @@ def available_notification_agents():
|
|||
'on_extdown': plexpy.CONFIG.IFTTT_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.IFTTT_ON_INTDOWN,
|
||||
'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',
|
||||
'id': AGENT_IDS['Telegram'],
|
||||
|
@ -279,7 +291,8 @@ def available_notification_agents():
|
|||
'on_extdown': plexpy.CONFIG.TELEGRAM_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.TELEGRAM_ON_INTDOWN,
|
||||
'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',
|
||||
'id': AGENT_IDS['Slack'],
|
||||
|
@ -296,7 +309,8 @@ def available_notification_agents():
|
|||
'on_extdown': plexpy.CONFIG.SLACK_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.SLACK_ON_INTDOWN,
|
||||
'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',
|
||||
'id': AGENT_IDS['Scripts'],
|
||||
|
@ -313,7 +327,8 @@ def available_notification_agents():
|
|||
'on_extdown': plexpy.CONFIG.SCRIPTS_ON_EXTDOWN,
|
||||
'on_extup': plexpy.CONFIG.SCRIPTS_ON_EXTUP,
|
||||
'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',
|
||||
'id': AGENT_IDS['Facebook'],
|
||||
|
@ -330,7 +345,8 @@ def available_notification_agents():
|
|||
'on_extdown': plexpy.CONFIG.FACEBOOK_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.FACEBOOK_ON_INTDOWN,
|
||||
'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_intdown': plexpy.CONFIG.OSX_NOTIFY_ON_INTDOWN,
|
||||
'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
|
||||
|
@ -1266,7 +1283,7 @@ class TwitterNotifier(object):
|
|||
|
||||
def return_config_options(self):
|
||||
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>\
|
||||
Step 2: Go to <strong>Keys and Access Tokens</strong> and click \
|
||||
<strong>Create my access token</strong>.<br>\
|
||||
|
@ -1301,7 +1318,7 @@ class TwitterNotifier(object):
|
|||
{'label': 'Include Subject Line',
|
||||
'value': self.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'
|
||||
}
|
||||
]
|
||||
|
@ -1625,7 +1642,8 @@ class IFTTT(object):
|
|||
config_option = [{'label': 'Ifttt Maker Channel Key',
|
||||
'value': self.apikey,
|
||||
'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'
|
||||
},
|
||||
{'label': 'Ifttt Event',
|
||||
|
@ -1699,19 +1717,23 @@ class TELEGRAM(object):
|
|||
config_option = [{'label': 'Telegram Bot Token',
|
||||
'value': self.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'
|
||||
},
|
||||
{'label': 'Telegram Chat ID, Group ID, or Channel Username',
|
||||
'value': self.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'
|
||||
},
|
||||
{'label': 'Include Subject Line',
|
||||
'value': self.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'
|
||||
}
|
||||
]
|
||||
|
@ -1809,7 +1831,7 @@ class SLACK(object):
|
|||
{'label': 'Include Subject Line',
|
||||
'value': self.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'
|
||||
}
|
||||
]
|
||||
|
@ -1880,14 +1902,14 @@ class Scripts(object):
|
|||
elif notify_action == 'resume':
|
||||
script = plexpy.CONFIG.SCRIPTS_ON_RESUME_SCRIPT
|
||||
|
||||
elif notify_action == 'watched':
|
||||
script = plexpy.CONFIG.SCRIPTS_ON_WATCHED_SCRIPT
|
||||
|
||||
elif notify_action == 'buffer':
|
||||
script = plexpy.CONFIG.SCRIPTS_ON_BUFFER_SCRIPT
|
||||
|
||||
elif notify_action == 'extdown':
|
||||
script = plexpy.CONFIG.SCRIPTS_ON_EXTDOWN_SCRIPT
|
||||
|
||||
elif notify_action == 'extup':
|
||||
script = plexpy.CONFIG.SCRIPTS_ON_EXTUP_SCRIPT
|
||||
elif notify_action == 'created':
|
||||
script = plexpy.CONFIG.SCRIPTS_ON_CREATED_SCRIPT
|
||||
|
||||
elif notify_action == 'intdown':
|
||||
script = plexpy.CONFIG.SCRIPTS_ON_INTDOWN_SCRIPT
|
||||
|
@ -1895,11 +1917,14 @@ class Scripts(object):
|
|||
elif notify_action == 'intup':
|
||||
script = plexpy.CONFIG.SCRIPTS_ON_INTUP_SCRIPT
|
||||
|
||||
elif notify_action == 'created':
|
||||
script = plexpy.CONFIG.SCRIPTS_ON_CREATED_SCRIPT
|
||||
elif notify_action == 'extdown':
|
||||
script = plexpy.CONFIG.SCRIPTS_ON_EXTDOWN_SCRIPT
|
||||
|
||||
elif notify_action == 'watched':
|
||||
script = plexpy.CONFIG.SCRIPTS_ON_WATCHED_SCRIPT
|
||||
elif notify_action == 'extup':
|
||||
script = plexpy.CONFIG.SCRIPTS_ON_EXTUP_SCRIPT
|
||||
|
||||
elif notify_action == 'pmsupdate':
|
||||
script = plexpy.CONFIG.SCRIPTS_ON_PMSUPDATE_SCRIPT
|
||||
|
||||
else:
|
||||
# For manual scripts
|
||||
|
@ -2041,13 +2066,6 @@ class Scripts(object):
|
|||
'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 Server Down',
|
||||
'value': plexpy.CONFIG.SCRIPTS_ON_INTDOWN_SCRIPT,
|
||||
'name': 'scripts_on_intdown_script',
|
||||
|
@ -2055,6 +2073,20 @@ class Scripts(object):
|
|||
'input_type': 'select',
|
||||
'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',
|
||||
'value': plexpy.CONFIG.SCRIPTS_ON_EXTUP_SCRIPT,
|
||||
'name': 'scripts_on_extup_script',
|
||||
|
@ -2062,10 +2094,10 @@ class Scripts(object):
|
|||
'input_type': 'select',
|
||||
'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.',
|
||||
{'label': 'Plex Update Available',
|
||||
'value': plexpy.CONFIG.SCRIPTS_ON_PMSUPDATE_SCRIPT,
|
||||
'name': 'scripts_on_pmsupdate_script',
|
||||
'description': 'Choose the script for Plex update available.',
|
||||
'input_type': 'select',
|
||||
'select_options': self.list_scripts()
|
||||
}
|
||||
|
@ -2082,6 +2114,7 @@ class FacebookNotifier(object):
|
|||
self.app_id = plexpy.CONFIG.FACEBOOK_APP_ID
|
||||
self.app_secret = plexpy.CONFIG.FACEBOOK_APP_SECRET
|
||||
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_subject = plexpy.CONFIG.FACEBOOK_INCL_SUBJECT
|
||||
|
||||
|
@ -2139,10 +2172,27 @@ class FacebookNotifier(object):
|
|||
poster_url = metadata.get('poster_url','')
|
||||
|
||||
if poster_url:
|
||||
if metadata['media_type'] == 'movie' or metadata['media_type'] == 'show':
|
||||
if metadata['media_type'] == 'movie':
|
||||
title = metadata['title']
|
||||
subtitle = metadata['year']
|
||||
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':
|
||||
title = '%s - %s' % (metadata['grandparent_title'], metadata['title'])
|
||||
|
@ -2150,26 +2200,44 @@ class FacebookNotifier(object):
|
|||
'\xc2\xb7'.decode('utf8'),
|
||||
metadata['media_index'])
|
||||
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':
|
||||
title = metadata['title']
|
||||
subtitle = ''
|
||||
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':
|
||||
title = '%s - %s' % (metadata['grandparent_title'], metadata['title'])
|
||||
subtitle = metadata['parent_title']
|
||||
rating_key = metadata['parent_rating_key']
|
||||
|
||||
caption = 'View in Plex Web.'
|
||||
if metadata.get('lastfm_url',''):
|
||||
poster_link = metadata.get('lastfm_url', '')
|
||||
caption = 'View on Last.fm.'
|
||||
|
||||
# Build Facebook post attachment
|
||||
attachment['link'] = 'http://app.plex.tv/web/app#!/server/' + plexpy.CONFIG.PMS_IDENTIFIER + \
|
||||
'/details/%2Flibrary%2Fmetadata%2F' + rating_key
|
||||
if self.incl_pmslink:
|
||||
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['name'] = title
|
||||
attachment['description'] = subtitle
|
||||
attachment['caption'] = caption
|
||||
|
||||
try:
|
||||
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):
|
||||
config_option = [{'label': 'Instructions',
|
||||
'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>\
|
||||
Step 2: Go to <strong>Settings > Basic</strong> and fill in a \
|
||||
<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 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 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.',
|
||||
'input_type': 'help'
|
||||
},
|
||||
|
@ -2232,13 +2300,20 @@ class FacebookNotifier(object):
|
|||
{'label': 'Include Poster Image',
|
||||
'value': self.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'
|
||||
},
|
||||
{'label': 'Include Subject Line',
|
||||
'value': self.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'
|
||||
}
|
||||
]
|
||||
|
|
|
@ -251,7 +251,7 @@ def import_from_plexwatch(database=None, table_name=None, import_ignore_interval
|
|||
hours=0, minutes=0, seconds=0)
|
||||
plexpy.schedule_job(activity_pinger.check_recently_added, 'Check for recently added items',
|
||||
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)
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
# If the user_id no longer exists in the friends list, pull it from the xml.
|
||||
|
|
|
@ -389,6 +389,38 @@ class PmsConnect(object):
|
|||
|
||||
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'):
|
||||
"""
|
||||
Return processed and validated list of recently added items.
|
||||
|
@ -1008,7 +1040,7 @@ class PmsConnect(object):
|
|||
'user_id': user_details['user_id'],
|
||||
'friendly_name': user_details['friendly_name'],
|
||||
'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'),
|
||||
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
|
||||
'machine_id': machine_id,
|
||||
|
@ -1130,7 +1162,7 @@ class PmsConnect(object):
|
|||
'user_id': user_details['user_id'],
|
||||
'friendly_name': user_details['friendly_name'],
|
||||
'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'),
|
||||
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
|
||||
'machine_id': machine_id,
|
||||
|
@ -1188,7 +1220,7 @@ class PmsConnect(object):
|
|||
'user_id': user_details['user_id'],
|
||||
'friendly_name': user_details['friendly_name'],
|
||||
'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'),
|
||||
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
|
||||
'machine_id': machine_id,
|
||||
|
@ -1246,7 +1278,7 @@ class PmsConnect(object):
|
|||
'user_id': user_details['user_id'],
|
||||
'friendly_name': user_details['friendly_name'],
|
||||
'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'),
|
||||
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
|
||||
'machine_id': machine_id,
|
||||
|
@ -1337,7 +1369,7 @@ class PmsConnect(object):
|
|||
'user_id': user_details['user_id'],
|
||||
'friendly_name': user_details['friendly_name'],
|
||||
'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'),
|
||||
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
|
||||
'machine_id': machine_id,
|
||||
|
@ -1479,7 +1511,7 @@ class PmsConnect(object):
|
|||
xml_head = identity.getElementsByTagName('MediaContainer')
|
||||
except Exception as e:
|
||||
logger.warn(u"PlexPy Pmsconnect :: Unable to parse XML for get_local_server_identity: %s." % e)
|
||||
return []
|
||||
return {}
|
||||
|
||||
server_identity = {}
|
||||
for a in xml_head:
|
||||
|
@ -1995,3 +2027,40 @@ class PmsConnect(object):
|
|||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -50,8 +50,7 @@ class Users(object):
|
|||
'session_history_metadata.year',
|
||||
'session_history_metadata.media_index',
|
||||
'session_history_metadata.parent_media_index',
|
||||
'session_history_media_info.video_decision',
|
||||
'session_history_media_info.audio_decision',
|
||||
'session_history_media_info.transcode_decision',
|
||||
'users.do_notify as do_notify',
|
||||
'users.keep_history as keep_history'
|
||||
]
|
||||
|
@ -117,8 +116,7 @@ class Users(object):
|
|||
'year': item['year'],
|
||||
'media_index': item['media_index'],
|
||||
'parent_media_index': item['parent_media_index'],
|
||||
'video_decision': item['video_decision'],
|
||||
'audio_decision': item['audio_decision'],
|
||||
'transcode_decision': item['transcode_decision'],
|
||||
'do_notify': helpers.checked(item['do_notify']),
|
||||
'keep_history': helpers.checked(item['keep_history'])
|
||||
}
|
||||
|
@ -154,8 +152,7 @@ class Users(object):
|
|||
'session_history_metadata.year',
|
||||
'session_history_metadata.media_index',
|
||||
'session_history_metadata.parent_media_index',
|
||||
'session_history_media_info.video_decision',
|
||||
'session_history_media_info.audio_decision',
|
||||
'session_history_media_info.transcode_decision',
|
||||
'session_history.user',
|
||||
'session_history.user_id as custom_user_id',
|
||||
'(CASE WHEN users.friendly_name IS NULL THEN users.username ELSE \
|
||||
|
@ -213,8 +210,7 @@ class Users(object):
|
|||
'year': item['year'],
|
||||
'media_index': item['media_index'],
|
||||
'parent_media_index': item['parent_media_index'],
|
||||
'video_decision': item['video_decision'],
|
||||
'audio_decision': item['audio_decision'],
|
||||
'transcode_decision': item['transcode_decision'],
|
||||
'friendly_name': item['friendly_name']
|
||||
}
|
||||
|
||||
|
@ -245,7 +241,7 @@ class Users(object):
|
|||
def get_details(self, user_id=None, user=None):
|
||||
from plexpy import plextv
|
||||
|
||||
default_return = {'user_id': None,
|
||||
default_return = {'user_id': 0,
|
||||
'username': 'Local',
|
||||
'friendly_name': 'Local',
|
||||
'user_thumb': common.DEFAULT_USER_THUMB,
|
||||
|
@ -254,7 +250,7 @@ class Users(object):
|
|||
'is_allow_sync': 0,
|
||||
'is_restricted': 0,
|
||||
'do_notify': 0,
|
||||
'keep_history': 0
|
||||
'keep_history': 1
|
||||
}
|
||||
|
||||
if not user_id and not user:
|
||||
|
@ -316,7 +312,8 @@ class Users(object):
|
|||
return user_details
|
||||
|
||||
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
|
||||
plextv.refresh_users()
|
||||
|
||||
|
@ -326,7 +323,8 @@ class Users(object):
|
|||
return user_details
|
||||
|
||||
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
|
||||
# Use "Local" user to retain compatibility with PlexWatch database value
|
||||
return default_return
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
PLEXPY_VERSION = "master"
|
||||
PLEXPY_RELEASE_VERSION = "1.3.9"
|
||||
PLEXPY_RELEASE_VERSION = "1.3.10"
|
||||
|
|
|
@ -25,6 +25,7 @@ import websocket
|
|||
|
||||
name = 'websocket'
|
||||
opcode_data = (websocket.ABNF.OPCODE_TEXT, websocket.ABNF.OPCODE_BINARY)
|
||||
ws_reconnect = False
|
||||
|
||||
|
||||
def start_thread():
|
||||
|
@ -34,6 +35,11 @@ def start_thread():
|
|||
threading.Thread(target=run).start()
|
||||
|
||||
|
||||
def reconnect():
|
||||
global ws_reconnect
|
||||
ws_reconnect = True
|
||||
|
||||
|
||||
def run():
|
||||
from websocket import create_connection
|
||||
|
||||
|
@ -51,19 +57,21 @@ def run():
|
|||
if plexpy.CONFIG.PMS_TOKEN:
|
||||
uri += '?X-Plex-Token=' + plexpy.CONFIG.PMS_TOKEN
|
||||
|
||||
global ws_reconnect
|
||||
ws_reconnect = False
|
||||
ws_connected = False
|
||||
reconnects = 0
|
||||
|
||||
# Try an open the websocket connection - if it fails after 15 retries fallback to polling
|
||||
while not ws_connected and reconnects <= 15:
|
||||
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)
|
||||
reconnects = 0
|
||||
ws_connected = True
|
||||
logger.info(u'PlexPy WebSocket :: Ready')
|
||||
logger.info(u"PlexPy WebSocket :: Ready")
|
||||
except IOError, e:
|
||||
logger.error(u'PlexPy WebSocket :: %s.' % e)
|
||||
logger.error(u"PlexPy WebSocket :: %s." % e)
|
||||
reconnects += 1
|
||||
time.sleep(5)
|
||||
|
||||
|
@ -81,22 +89,30 @@ def run():
|
|||
if reconnects > 1:
|
||||
time.sleep(5)
|
||||
|
||||
logger.warn(u'PlexPy WebSocket :: Connection has closed, reconnecting...')
|
||||
logger.warn(u"PlexPy WebSocket :: Connection has closed, reconnecting...")
|
||||
try:
|
||||
ws = create_connection(uri)
|
||||
except IOError, e:
|
||||
logger.info(u'PlexPy WebSocket :: %s.' % e)
|
||||
logger.info(u"PlexPy WebSocket :: %s." % e)
|
||||
|
||||
else:
|
||||
ws.shutdown()
|
||||
ws_connected = False
|
||||
break
|
||||
|
||||
if not ws_connected:
|
||||
logger.error(u'PlexPy WebSocket :: Connection unavailable, falling back to polling.')
|
||||
# Check if we recieved a restart notification and close websocket connection cleanly
|
||||
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.initialize_scheduler()
|
||||
|
||||
logger.debug(u'PlexPy WebSocket :: Leaving thread.')
|
||||
logger.debug(u"PlexPy WebSocket :: Leaving thread.")
|
||||
|
||||
|
||||
def receive(ws):
|
||||
|
@ -124,7 +140,7 @@ def process(opcode, data):
|
|||
try:
|
||||
info = json.loads(data)
|
||||
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)
|
||||
return False
|
||||
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# 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 mako.lookup import TemplateLookup
|
||||
|
@ -253,6 +254,18 @@ class WebInterface(object):
|
|||
logger.warn(u"Unable to retrieve data for get_recently_added.")
|
||||
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 #####
|
||||
|
||||
|
@ -776,6 +789,10 @@ class WebInterface(object):
|
|||
media_type = kwargs.get('media_type', "")
|
||||
if media_type != 'all':
|
||||
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()
|
||||
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.")
|
||||
|
||||
@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 #####
|
||||
|
@ -1091,6 +1108,19 @@ class WebInterface(object):
|
|||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
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
|
||||
def clearLogs(self):
|
||||
plexpy.LOG_LIST = []
|
||||
|
@ -1147,8 +1177,9 @@ class WebInterface(object):
|
|||
"api_key": plexpy.CONFIG.API_KEY,
|
||||
"update_db_interval": plexpy.CONFIG.UPDATE_DB_INTERVAL,
|
||||
"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,
|
||||
"log_dir": plexpy.CONFIG.LOG_DIR,
|
||||
"check_github": checked(plexpy.CONFIG.CHECK_GITHUB),
|
||||
"interface_list": interface_list,
|
||||
"cache_sizemb": plexpy.CONFIG.CACHE_SIZEMB,
|
||||
|
@ -1178,6 +1209,7 @@ class WebInterface(object):
|
|||
"tv_notify_on_pause": checked(plexpy.CONFIG.TV_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),
|
||||
"monitor_pms_updates": checked(plexpy.CONFIG.MONITOR_PMS_UPDATES),
|
||||
"monitor_remote_access": checked(plexpy.CONFIG.MONITOR_REMOTE_ACCESS),
|
||||
"monitoring_interval": plexpy.CONFIG.MONITORING_INTERVAL,
|
||||
"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_intup_subject_text": plexpy.CONFIG.NOTIFY_ON_INTUP_SUBJECT_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,
|
||||
"home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH,
|
||||
"home_stats_type": checked(plexpy.CONFIG.HOME_STATS_TYPE),
|
||||
|
@ -1246,12 +1280,15 @@ class WebInterface(object):
|
|||
"refresh_libraries_on_startup", "refresh_users_on_startup",
|
||||
"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",
|
||||
"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:
|
||||
if checked_config not in kwargs:
|
||||
# checked items should be zero or one. if they were not sent then the item was not checked
|
||||
kwargs[checked_config] = '0'
|
||||
kwargs[checked_config] = 0
|
||||
else:
|
||||
kwargs[checked_config] = 1
|
||||
|
||||
# If http password exists in config, do not overwrite when blank value received
|
||||
if kwargs.get('http_password'):
|
||||
|
@ -1274,13 +1311,14 @@ class WebInterface(object):
|
|||
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_users_interval') != str(plexpy.CONFIG.REFRESH_USERS_INTERVAL) or \
|
||||
kwargs.get('notify_recently_added') != str(plexpy.CONFIG.NOTIFY_RECENTLY_ADDED) or \
|
||||
kwargs.get('monitor_remote_access') != str(plexpy.CONFIG.MONITOR_REMOTE_ACCESS):
|
||||
kwargs.get('notify_recently_added') != plexpy.CONFIG.NOTIFY_RECENTLY_ADDED or \
|
||||
kwargs.get('monitor_pms_updates') != plexpy.CONFIG.MONITOR_PMS_UPDATES or \
|
||||
kwargs.get('monitor_remote_access') != plexpy.CONFIG.MONITOR_REMOTE_ACCESS:
|
||||
reschedule = True
|
||||
|
||||
# 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 \
|
||||
kwargs.get('pms_is_remote') != str(plexpy.CONFIG.PMS_IS_REMOTE):
|
||||
if kwargs.get('pms_ssl') != plexpy.CONFIG.PMS_SSL or \
|
||||
kwargs.get('pms_is_remote') != plexpy.CONFIG.PMS_IS_REMOTE:
|
||||
server_changed = True
|
||||
|
||||
# If we change the HTTPS setting, make sure we generate a new certificate.
|
||||
|
@ -1327,6 +1365,7 @@ class WebInterface(object):
|
|||
if server_changed:
|
||||
plextv.get_real_pms_url()
|
||||
pmsconnect.get_server_friendly_name()
|
||||
web_socket.reconnect()
|
||||
|
||||
# Reconfigure scheduler if intervals changed
|
||||
if reschedule:
|
||||
|
@ -1630,11 +1669,16 @@ class WebInterface(object):
|
|||
if source == 'history':
|
||||
data_factory = datafactory.DataFactory()
|
||||
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:
|
||||
pms_connect = pmsconnect.PmsConnect()
|
||||
result = pms_connect.get_metadata_details(rating_key=rating_key, get_media_info=True)
|
||||
if result:
|
||||
metadata = result['metadata']
|
||||
data_factory = datafactory.DataFactory()
|
||||
poster_url = data_factory.get_poster_url(metadata=metadata)
|
||||
metadata['poster_url'] = poster_url
|
||||
|
||||
if metadata:
|
||||
return serve_template(templatename="info.html", data=metadata, title="Info", config=config, source=source)
|
||||
|
@ -1681,6 +1725,22 @@ class WebInterface(object):
|
|||
|
||||
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 #####
|
||||
|
||||
|
@ -2183,3 +2243,9 @@ class WebInterface(object):
|
|||
a = Api()
|
||||
a.checkParams(*args, **kwargs)
|
||||
return a.fetchData()
|
||||
|
||||
@cherrypy.expose
|
||||
def check_pms_updater(self):
|
||||
pms_connect = pmsconnect.PmsConnect()
|
||||
result = pms_connect.get_update_staus()
|
||||
return json.dumps(result)
|
Loading…
Add table
Add a link
Reference in a new issue