mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-11 15:56:07 -07:00
Merge branch 'nightly' into python3
# Conflicts: # plexpy/database.py # plexpy/datafactory.py # plexpy/libraries.py # plexpy/users.py
This commit is contained in:
commit
798c17706c
25 changed files with 599 additions and 405 deletions
51
API.md
51
API.md
|
@ -88,7 +88,8 @@ Required parameters:
|
|||
section_id (str): The id of the Plex library section
|
||||
|
||||
Optional parameters:
|
||||
None
|
||||
server_id (str): The Plex server identifier of the library section
|
||||
row_ids (str): Comma separated row ids to delete, e.g. "2,3,8"
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
@ -103,7 +104,7 @@ Required parameters:
|
|||
user_id (str): The id of the Plex user
|
||||
|
||||
Optional parameters:
|
||||
None
|
||||
row_ids (str): Comma separated row ids to delete, e.g. "2,3,8"
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
@ -114,6 +115,21 @@ Returns:
|
|||
Delete and recreate the cache directory.
|
||||
|
||||
|
||||
### delete_history
|
||||
Delete history rows from Tautulli.
|
||||
|
||||
```
|
||||
Required parameters:
|
||||
row_ids (str): Comma separated row ids to delete, e.g. "65,110,2,3645"
|
||||
|
||||
Optional parameters:
|
||||
None
|
||||
|
||||
Returns:
|
||||
None
|
||||
```
|
||||
|
||||
|
||||
### delete_hosted_images
|
||||
Delete the images uploaded to image hosting services.
|
||||
|
||||
|
@ -146,7 +162,8 @@ Required parameters:
|
|||
section_id (str): The id of the Plex library section
|
||||
|
||||
Optional parameters:
|
||||
None
|
||||
server_id (str): The Plex server identifier of the library section
|
||||
row_ids (str): Comma separated row ids to delete, e.g. "2,3,8"
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
@ -294,7 +311,7 @@ Required parameters:
|
|||
user_id (str): The id of the Plex user
|
||||
|
||||
Optional parameters:
|
||||
None
|
||||
row_ids (str): Comma separated row ids to delete, e.g. "2,3,8"
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
@ -726,7 +743,6 @@ Returns:
|
|||
"group_count": 1,
|
||||
"group_ids": "1124",
|
||||
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
|
||||
"id": 1124,
|
||||
"ip_address": "xxx.xxx.xxx.xxx",
|
||||
"live": 0,
|
||||
"media_index": 17,
|
||||
|
@ -742,6 +758,7 @@ Returns:
|
|||
"player": "Castle-PC",
|
||||
"rating_key": 4348,
|
||||
"reference_id": 1123,
|
||||
"row_id": 1124,
|
||||
"session_key": null,
|
||||
"started": 1462688107,
|
||||
"state": null,
|
||||
|
@ -853,6 +870,7 @@ Returns:
|
|||
[{"art": "/:/resources/show-fanart.jpg",
|
||||
"child_count": "3745",
|
||||
"count": "62",
|
||||
"is_active": 1,
|
||||
"parent_count": "240",
|
||||
"section_id": "2",
|
||||
"section_name": "TV Shows",
|
||||
|
@ -894,7 +912,8 @@ Returns:
|
|||
"do_notify_created": "Checked",
|
||||
"duration": 1578037,
|
||||
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
|
||||
"id": 1128,
|
||||
"histroy_row_id": 1128,
|
||||
"is_active": 1,
|
||||
"keep_history": "Checked",
|
||||
"labels": [],
|
||||
"last_accessed": 1462693216,
|
||||
|
@ -910,9 +929,11 @@ Returns:
|
|||
"parent_title": "",
|
||||
"plays": 772,
|
||||
"rating_key": 153037,
|
||||
"row_id": 1,
|
||||
"section_id": 2,
|
||||
"section_name": "TV Shows",
|
||||
"section_type": "Show",
|
||||
"server_id": "ds48g4r354a8v9byrrtr697g3g79w",
|
||||
"thumb": "/library/metadata/153036/thumb/1462175062",
|
||||
"year": 2016
|
||||
},
|
||||
|
@ -940,13 +961,16 @@ Returns:
|
|||
"deleted_section": 0,
|
||||
"do_notify": 1,
|
||||
"do_notify_created": 1,
|
||||
"is_active": 1,
|
||||
"keep_history": 1,
|
||||
"library_art": "/:/resources/movie-fanart.jpg",
|
||||
"library_thumb": "/:/resources/movie.png",
|
||||
"parent_count": null,
|
||||
"row_id": 1,
|
||||
"section_id": 1,
|
||||
"section_name": "Movies",
|
||||
"section_type": "movie"
|
||||
"section_type": "movie",
|
||||
"server_id": "ds48g4r354a8v9byrrtr697g3g79w"
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -1066,6 +1090,7 @@ Required parameters:
|
|||
|
||||
Optional parameters:
|
||||
grouping (int): 0 or 1
|
||||
query_days (str): Comma separated days, e.g. "1,7,30,0"
|
||||
|
||||
Returns:
|
||||
json:
|
||||
|
@ -2222,10 +2247,13 @@ Returns:
|
|||
"do_notify": 1,
|
||||
"email": "Jon.Snow.1337@CastleBlack.com",
|
||||
"friendly_name": "Jon Snow",
|
||||
"is_active": 1,
|
||||
"is_admin": 0,
|
||||
"is_allow_sync": 1,
|
||||
"is_home_user": 1,
|
||||
"is_restricted": 0,
|
||||
"keep_history": 1,
|
||||
"row_id": 1,
|
||||
"shared_libraries": ["10", "1", "4", "5", "15", "20", "2"],
|
||||
"user_id": 133788,
|
||||
"user_thumb": "https://plex.tv/users/k10w42309cynaopq/avatar",
|
||||
|
@ -2378,6 +2406,7 @@ Required parameters:
|
|||
|
||||
Optional parameters:
|
||||
grouping (int): 0 or 1
|
||||
query_days (str): Comma separated days, e.g. "1,7,30,0"
|
||||
|
||||
Returns:
|
||||
json:
|
||||
|
@ -2421,11 +2450,13 @@ Returns:
|
|||
"filter_music": "",
|
||||
"filter_photos": "",
|
||||
"filter_tv": "",
|
||||
"is_active": 1,
|
||||
"is_admin": 0,
|
||||
"is_allow_sync": 1,
|
||||
"is_home_user": 1,
|
||||
"is_restricted": 0,
|
||||
"keep_history": 1,
|
||||
"row_id": 1,
|
||||
"server_token": "PU9cMuQZxJKFBtGqHk68",
|
||||
"shared_libraries": "1;2;3",
|
||||
"thumb": "https://plex.tv/users/k10w42309cynaopq/avatar",
|
||||
|
@ -2465,8 +2496,9 @@ Returns:
|
|||
"duration": 2998290,
|
||||
"friendly_name": "Jon Snow",
|
||||
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
|
||||
"id": 1121,
|
||||
"history_row_id": 1121,
|
||||
"ip_address": "xxx.xxx.xxx.xxx",
|
||||
"is_active": 1,
|
||||
"keep_history": "Checked",
|
||||
"last_played": "Game of Thrones - The Red Woman",
|
||||
"last_seen": 1462591869,
|
||||
|
@ -2480,6 +2512,7 @@ Returns:
|
|||
"player": "Plex Web (Chrome)",
|
||||
"plays": 487,
|
||||
"rating_key": 153037,
|
||||
"row_id": 1,
|
||||
"thumb": "/library/metadata/153036/thumb/1462175062",
|
||||
"transcode_decision": "transcode",
|
||||
"user_id": 133788,
|
||||
|
@ -2741,7 +2774,7 @@ Returns:
|
|||
### sql
|
||||
Query the Tautulli database with raw SQL. Automatically makes a backup of
|
||||
the database if the latest backup is older then 24h. `api_sql` must be
|
||||
manually enabled in the config file.
|
||||
manually enabled in the config file while Tautulli is shut down.
|
||||
|
||||
```
|
||||
Required parameters:
|
||||
|
|
|
@ -711,7 +711,6 @@ fieldset[disabled] .form-control {
|
|||
box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1);
|
||||
}
|
||||
.users-poster-face {
|
||||
overflow: hidden;
|
||||
float: left;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
|
@ -857,7 +856,6 @@ a .users-poster-face:hover {
|
|||
z-index: 2;
|
||||
}
|
||||
.dashboard-activity-info-platform {
|
||||
padding: 6px !important;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
width: 50px;
|
||||
|
@ -3119,6 +3117,21 @@ div.dataTables_info {
|
|||
font-weight: bold;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.inactive-library-tooltip,
|
||||
.inactive-user-tooltip {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.inactive-library-tooltip i.fa,
|
||||
.inactive-user-tooltip i.fa {
|
||||
color: #E5A00D;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
text-shadow: 0 0 2px rgba(0,0,0,.5);
|
||||
}
|
||||
.history-thumbnail-popover {
|
||||
z-index: 2000;
|
||||
padding: 0;
|
||||
|
@ -3808,9 +3821,8 @@ a:hover .overlay-refresh-image:hover {
|
|||
}
|
||||
|
||||
.svg-icon {
|
||||
padding: 10px;
|
||||
background-size: calc(100% - 20px) calc(100% - 20px) !important;
|
||||
background-origin: content-box !important;
|
||||
background-size: contain !important;
|
||||
background-repeat: no-repeat !important;
|
||||
background-position: center !important;
|
||||
}
|
||||
|
|
|
@ -143,7 +143,7 @@ DOCUMENTATION :: END
|
|||
<div id="platform-${sk}" class="dashboard-activity-info-platform${no_terminate} svg-icon platform-${data['platform_name']}" title="${data['platform']}"></div>
|
||||
% if _session['user_group'] == 'admin' and plexpy.CONFIG.PMS_PLEXPASS and data['session_id']:
|
||||
<div class="dashboard-activity-terminate-session" id="terminate-button-${sk}" data-key="${sk}" data-id="${data['session_id']}" data-toggle="tooltip" title="Terminate Stream">
|
||||
<i class="fa fa-times" style="padding-top: 8px;"></i>
|
||||
<i class="fa fa-times" style="padding-top: 10px;"></i>
|
||||
</div>
|
||||
% endif
|
||||
</div>
|
||||
|
|
|
@ -185,18 +185,16 @@
|
|||
$('#deleteCount').text(history_to_delete.length);
|
||||
$('#confirm-modal-delete').modal();
|
||||
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
|
||||
history_to_delete.forEach(function(row, idx) {
|
||||
$.ajax({
|
||||
url: 'delete_history_rows',
|
||||
type: 'POST',
|
||||
data: { row_id: row },
|
||||
data: { row_ids: history_to_delete.join(',') },
|
||||
async: true,
|
||||
success: function (data) {
|
||||
var msg = "History deleted";
|
||||
showMsg(msg, false, true, 2000);
|
||||
}
|
||||
});
|
||||
});
|
||||
history_table.draw();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -721,18 +721,16 @@ DOCUMENTATION :: END
|
|||
$('#deleteCount').text(history_to_delete.length);
|
||||
$('#confirm-modal-delete').modal();
|
||||
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
|
||||
history_to_delete.forEach(function (row, idx) {
|
||||
$.ajax({
|
||||
url: 'delete_history_rows',
|
||||
type: 'POST',
|
||||
data: { row_id: row },
|
||||
data: { row_ids: history_to_delete.join(',') },
|
||||
async: true,
|
||||
success: function (data) {
|
||||
var msg = "History deleted";
|
||||
showMsg(msg, false, true, 2000);
|
||||
}
|
||||
});
|
||||
});
|
||||
history_table.draw();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -36,10 +36,10 @@ history_table_options = {
|
|||
"targets": [0],
|
||||
"data": null,
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (rowData['id'] === null) {
|
||||
if (rowData['row_id'] === null) {
|
||||
$(td).html('');
|
||||
} else {
|
||||
$(td).html('<button class="btn btn-xs btn-warning" data-id="' + rowData['id'] + '"><i class="fa fa-trash-o fa-fw"></i> Delete</button>');
|
||||
$(td).html('<button class="btn btn-xs btn-warning" data-id="' + rowData['row_id'] + '"><i class="fa fa-trash-o fa-fw"></i> Delete</button>');
|
||||
}
|
||||
},
|
||||
"width": "5%",
|
||||
|
@ -317,19 +317,19 @@ history_table_options = {
|
|||
"rowCallback": function (row, rowData, rowIndex) {
|
||||
if (rowData['group_count'] == 1) {
|
||||
// if no grouped rows simply toggle the delete button
|
||||
if ($.inArray(rowData['id'], history_to_delete) !== -1) {
|
||||
$(row).find('button[data-id="' + rowData['id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger');
|
||||
if ($.inArray(rowData['row_id'], history_to_delete) !== -1) {
|
||||
$(row).find('button[data-id="' + rowData['row_id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger');
|
||||
}
|
||||
} else if (rowData['id'] !== null) {
|
||||
} else if (rowData['row_id'] !== null) {
|
||||
// if grouped rows
|
||||
// toggle the parent button to danger
|
||||
$(row).find('button[data-id="' + rowData['id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger');
|
||||
$(row).find('button[data-id="' + rowData['row_id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger');
|
||||
// check if any child rows are not selected
|
||||
var group_ids = rowData['group_ids'].split(',').map(Number);
|
||||
group_ids.forEach(function (id) {
|
||||
var index = $.inArray(id, history_to_delete);
|
||||
if (index == -1) {
|
||||
$(row).find('button[data-id="' + rowData['id'] + '"]').addClass('btn-warning').removeClass('btn-danger');
|
||||
$(row).find('button[data-id="' + rowData['row_id'] + '"]').addClass('btn-warning').removeClass('btn-danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -353,7 +353,7 @@ $('.history_table').on('click', '> tbody > tr > td.modal-control', function () {
|
|||
var rowData = row.data();
|
||||
|
||||
$.get('get_stream_data', {
|
||||
row_id: rowData['id'],
|
||||
row_id: rowData['row_id'],
|
||||
session_key: rowData['session_key'],
|
||||
user: rowData['friendly_name']
|
||||
}).then(function (jqXHR) {
|
||||
|
@ -382,9 +382,9 @@ $('.history_table').on('click', '> tbody > tr > td.delete-control > button', fun
|
|||
|
||||
if (rowData['group_count'] == 1) {
|
||||
// if no grouped rows simply add or remove row from history_to_delete
|
||||
var index = $.inArray(rowData['id'], history_to_delete);
|
||||
var index = $.inArray(rowData['row_id'], history_to_delete);
|
||||
if (index === -1) {
|
||||
history_to_delete.push(rowData['id']);
|
||||
history_to_delete.push(rowData['row_id']);
|
||||
} else {
|
||||
history_to_delete.splice(index, 1);
|
||||
}
|
||||
|
@ -549,7 +549,7 @@ function createChildTable(row, rowData) {
|
|||
var childRowData = childRow.data();
|
||||
|
||||
$.get('get_stream_data', {
|
||||
row_id: childRowData['id'],
|
||||
row_id: childRowData['row_id'],
|
||||
user: childRowData['friendly_name']
|
||||
}).then(function (jqXHR) {
|
||||
$("#info-modal").html(jqXHR);
|
||||
|
@ -576,9 +576,9 @@ function createChildTable(row, rowData) {
|
|||
var childRowData = childRow.data();
|
||||
|
||||
// add or remove row from history_to_delete
|
||||
var index = $.inArray(childRowData['id'], history_to_delete);
|
||||
var index = $.inArray(childRowData['row_id'], history_to_delete);
|
||||
if (index === -1) {
|
||||
history_to_delete.push(childRowData['id']);
|
||||
history_to_delete.push(childRowData['row_id']);
|
||||
} else {
|
||||
history_to_delete.splice(index, 1);
|
||||
}
|
||||
|
|
|
@ -169,7 +169,7 @@ $('.history_table').on('click', 'td.modal-control', function () {
|
|||
function showStreamDetails() {
|
||||
$.ajax({
|
||||
url: 'get_stream_data',
|
||||
data: { row_id: rowData['id'], user: rowData['friendly_name'] },
|
||||
data: { row_id: rowData['row_id'], user: rowData['friendly_name'] },
|
||||
cache: false,
|
||||
async: true,
|
||||
complete: function (xhr, status) {
|
||||
|
|
|
@ -27,8 +27,8 @@ libraries_list_table_options = {
|
|||
"data": null,
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
$(td).html('<div class="edit-library-toggles">' +
|
||||
'<button class="btn btn-xs btn-warning delete-library" data-id="' + rowData['section_id'] + '" data-toggle="button"><i class="fa fa-trash-o fa-fw"></i> Delete</button> ' +
|
||||
'<button class="btn btn-xs btn-warning purge-library" data-id="' + rowData['section_id'] + '" data-toggle="button"><i class="fa fa-eraser fa-fw"></i> Purge</button>   ' +
|
||||
'<button class="btn btn-xs btn-warning delete-library" data-id="' + rowData['row_id'] + '" data-toggle="button"><i class="fa fa-trash-o fa-fw"></i> Delete</button> ' +
|
||||
'<button class="btn btn-xs btn-warning purge-library" data-id="' + rowData['row_id'] + '" data-toggle="button"><i class="fa fa-eraser fa-fw"></i> Purge</button>   ' +
|
||||
'<input type="checkbox" id="keep_history-' + rowData['section_id'] + '" name="keep_history" value="1" ' + rowData['keep_history'] + '><label class="edit-tooltip" for="keep_history-' + rowData['section_id'] + '" data-toggle="tooltip" title="Toggle History"><i class="fa fa-history fa-lg fa-fw"></i></label> ' +
|
||||
'</div>');
|
||||
},
|
||||
|
@ -41,14 +41,16 @@ libraries_list_table_options = {
|
|||
"targets": [1],
|
||||
"data": "library_thumb",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
var inactive = '';
|
||||
if (!rowData['is_active']) { inactive = '<span class="inactive-library-tooltip" data-toggle="tooltip" title="Library not on Plex server"><i class="fa fa-exclamation-triangle"></i></span>'; }
|
||||
if (cellData !== null && cellData !== '') {
|
||||
if (rowData['library_thumb'].substring(0, 4) == "http") {
|
||||
$(td).html('<a href="library?section_id=' + rowData['section_id'] + '"><div class="libraries-poster-face" style="background-image: url(' + rowData['library_thumb'] + ');"></div></a>');
|
||||
$(td).html('<a href="' + page('library', rowData['section_id']) + '"><div class="libraries-poster-face" style="background-image: url(' + rowData['library_thumb'] + ');">' + inactive + '</div></a>');
|
||||
} else {
|
||||
$(td).html('<a href="library?section_id=' + rowData['section_id'] + '"><div class="libraries-poster-face svg-icon library-' + rowData['section_type'] + '"></div></a>');
|
||||
$(td).html('<a href="' + page('library', rowData['section_id']) + '"><div class="libraries-poster-face svg-icon library-' + rowData['section_type'] + '">' + inactive + '</div></a>');
|
||||
}
|
||||
} else {
|
||||
$(td).html('<a href="library?section_id=' + rowData['section_id'] + '"><div class="libraries-poster-face" style="background-image: url(../../images/cover.png);"></div></a>');
|
||||
$(td).html('<a href="' + page('library', rowData['section_id']) + '"><div class="libraries-poster-face" style="background-image: url(../../images/cover.png);">' + inactive + '</div></a>');
|
||||
}
|
||||
},
|
||||
"orderable": false,
|
||||
|
@ -61,8 +63,8 @@ libraries_list_table_options = {
|
|||
"data": "section_name",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== null && cellData !== '') {
|
||||
$(td).html('<div data-id="' + rowData['section_id'] + '">' +
|
||||
'<a href="library?section_id=' + rowData['section_id'] + '">' + cellData + '</a>' +
|
||||
$(td).html('<div data-id="' + rowData['row_id'] + '">' +
|
||||
'<a href="' + page('library', rowData['section_id']) + '">' + cellData + '</a>' +
|
||||
'</div>');
|
||||
} else {
|
||||
$(td).html('n/a');
|
||||
|
@ -232,11 +234,11 @@ libraries_list_table_options = {
|
|||
showMsg(msg, false, false, 0)
|
||||
},
|
||||
"rowCallback": function (row, rowData) {
|
||||
if ($.inArray(rowData['section_id'], libraries_to_delete) !== -1) {
|
||||
$(row).find('button.delete-library[data-id="' + rowData['section_id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger');
|
||||
if ($.inArray(rowData['row_id'], libraries_to_delete) !== -1) {
|
||||
$(row).find('button.delete-library[data-id="' + rowData['row_id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger');
|
||||
}
|
||||
if ($.inArray(rowData['section_id'], libraries_to_purge) !== -1) {
|
||||
$(row).find('button.purge-library[data-id="' + rowData['section_id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger');
|
||||
if ($.inArray(rowData['row_id'], libraries_to_purge) !== -1) {
|
||||
$(row).find('button.purge-library[data-id="' + rowData['row_id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -277,11 +279,11 @@ $('#libraries_list_table').on('click', 'td.edit-control > .edit-library-toggles
|
|||
var row = libraries_list_table.row(tr);
|
||||
var rowData = row.data();
|
||||
|
||||
var index_delete = $.inArray(rowData['section_id'], libraries_to_delete);
|
||||
var index_purge = $.inArray(rowData['section_id'], libraries_to_purge);
|
||||
var index_delete = $.inArray(rowData['row_id'], libraries_to_delete);
|
||||
var index_purge = $.inArray(rowData['row_id'], libraries_to_purge);
|
||||
|
||||
if (index_delete === -1) {
|
||||
libraries_to_delete.push(rowData['section_id']);
|
||||
libraries_to_delete.push(rowData['row_id']);
|
||||
if (index_purge === -1) {
|
||||
tr.find('button.purge-library').click();
|
||||
}
|
||||
|
@ -300,11 +302,11 @@ $('#libraries_list_table').on('click', 'td.edit-control > .edit-library-toggles
|
|||
var row = libraries_list_table.row(tr);
|
||||
var rowData = row.data();
|
||||
|
||||
var index_delete = $.inArray(rowData['section_id'], libraries_to_delete);
|
||||
var index_purge = $.inArray(rowData['section_id'], libraries_to_purge);
|
||||
var index_delete = $.inArray(rowData['row_id'], libraries_to_delete);
|
||||
var index_purge = $.inArray(rowData['row_id'], libraries_to_purge);
|
||||
|
||||
if (index_purge === -1) {
|
||||
libraries_to_purge.push(rowData['section_id']);
|
||||
libraries_to_purge.push(rowData['row_id']);
|
||||
} else {
|
||||
libraries_to_purge.splice(index_purge, 1);
|
||||
if (index_delete != -1) {
|
||||
|
|
|
@ -167,7 +167,7 @@ $('.user_ip_table').on('click', 'td.modal-control', function () {
|
|||
function showStreamDetails() {
|
||||
$.ajax({
|
||||
url: 'get_stream_data',
|
||||
data: { row_id: rowData['id'], user: rowData['friendly_name'] },
|
||||
data: { row_id: rowData['history_row_id'], user: rowData['friendly_name'] },
|
||||
cache: false,
|
||||
async: true,
|
||||
complete: function (xhr, status) {
|
||||
|
|
|
@ -44,8 +44,8 @@ users_list_table_options = {
|
|||
"data": null,
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
$(td).html('<div class="edit-user-toggles">' +
|
||||
'<button class="btn btn-xs btn-warning delete-user" data-id="' + rowData['user_id'] + '" data-toggle="button"><i class="fa fa-trash-o fa-fw"></i> Delete</button> ' +
|
||||
'<button class="btn btn-xs btn-warning purge-user" data-id="' + rowData['user_id'] + '" data-toggle="button"><i class="fa fa-eraser fa-fw"></i> Purge</button>   ' +
|
||||
'<button class="btn btn-xs btn-warning delete-user" data-id="' + rowData['row_id'] + '" data-toggle="button"><i class="fa fa-trash-o fa-fw"></i> Delete</button> ' +
|
||||
'<button class="btn btn-xs btn-warning purge-user" data-id="' + rowData['row_id'] + '" data-toggle="button"><i class="fa fa-eraser fa-fw"></i> Purge</button>   ' +
|
||||
'<input type="checkbox" id="keep_history-' + rowData['user_id'] + '" name="keep_history" value="1" ' + rowData['keep_history'] + '><label class="edit-tooltip" for="keep_history-' + rowData['user_id'] + '" data-toggle="tooltip" title="Toggle History"><i class="fa fa-history fa-lg fa-fw"></i></label> ' +
|
||||
'<input type="checkbox" id="allow_guest-' + rowData['user_id'] + '" name="allow_guest" value="1" ' + rowData['allow_guest'] + '><label class="edit-tooltip" for="allow_guest-' + rowData['user_id'] + '" data-toggle="tooltip" title="Toggle Guest Access"><i class="fa fa-unlock-alt fa-lg fa-fw"></i></label> ' +
|
||||
'</div>');
|
||||
|
@ -59,10 +59,12 @@ users_list_table_options = {
|
|||
"targets": [1],
|
||||
"data": "user_thumb",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
var inactive = '';
|
||||
if (!rowData['is_active']) { inactive = '<span class="inactive-user-tooltip" data-toggle="tooltip" title="User not on Plex server"><i class="fa fa-exclamation-triangle"></i></span>'; }
|
||||
if (cellData === '') {
|
||||
$(td).html('<a href="' + page('user', rowData['user_id']) + '"><div class="users-poster-face" style="background-image: url(../../images/gravatar-default-80x80.png);"></div></a>');
|
||||
$(td).html('<a href="' + page('user', rowData['user_id']) + '"><div class="users-poster-face" style="background-image: url(../../images/gravatar-default-80x80.png);">' + inactive + '</div></a>');
|
||||
} else {
|
||||
$(td).html('<a href="' + page('user', rowData['user_id']) + '"><div class="users-poster-face" style="background-image: url(' + rowData['user_thumb'] + ');"></div></a>');
|
||||
$(td).html('<a href="' + page('user', rowData['user_id']) + '"><div class="users-poster-face" style="background-image: url(' + rowData['user_thumb'] + ');">' + inactive + '</div></a>');
|
||||
}
|
||||
},
|
||||
"orderable": false,
|
||||
|
@ -75,7 +77,7 @@ users_list_table_options = {
|
|||
"data": "friendly_name",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== null && cellData !== '') {
|
||||
$(td).html('<div class="edit-user-name" data-id="' + rowData['user_id'] + '">' +
|
||||
$(td).html('<div class="edit-user-name" data-id="' + rowData['row_id'] + '">' +
|
||||
'<a href="' + page('user', rowData['user_id']) + '">' + cellData + '</a>' +
|
||||
'<input type="text" class="hidden" value="' + cellData + '">' +
|
||||
'</div>');
|
||||
|
@ -254,10 +256,10 @@ users_list_table_options = {
|
|||
},
|
||||
"rowCallback": function (row, rowData) {
|
||||
if ($.inArray(rowData['user_id'], users_to_delete) !== -1) {
|
||||
$(row).find('button.delete-user[data-id="' + rowData['user_id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger');
|
||||
$(row).find('button.delete-user[data-id="' + rowData['row_id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger');
|
||||
}
|
||||
if ($.inArray(rowData['user_id'], users_to_purge) !== -1) {
|
||||
$(row).find('button.purge-user[data-id="' + rowData['user_id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger');
|
||||
$(row).find('button.purge-user[data-id="' + rowData['row_id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -268,7 +270,7 @@ $('#users_list_table').on('click', 'td.modal-control', function () {
|
|||
var rowData = row.data();
|
||||
|
||||
$.get('get_stream_data', {
|
||||
row_id: rowData['id'],
|
||||
row_id: rowData['history_row_id'],
|
||||
user: rowData['friendly_name']
|
||||
}).then(function (jqXHR) {
|
||||
$("#info-modal").html(jqXHR);
|
||||
|
@ -326,11 +328,11 @@ $('#users_list_table').on('click', 'td.edit-control > .edit-user-toggles > butto
|
|||
var row = users_list_table.row(tr);
|
||||
var rowData = row.data();
|
||||
|
||||
var index_delete = $.inArray(rowData['user_id'], users_to_delete);
|
||||
var index_purge = $.inArray(rowData['user_id'], users_to_purge);
|
||||
var index_delete = $.inArray(rowData['row_id'], users_to_delete);
|
||||
var index_purge = $.inArray(rowData['row_id'], users_to_purge);
|
||||
|
||||
if (index_delete === -1) {
|
||||
users_to_delete.push(rowData['user_id']);
|
||||
users_to_delete.push(rowData['row_id']);
|
||||
if (index_purge === -1) {
|
||||
tr.find('button.purge-user').click();
|
||||
}
|
||||
|
@ -349,11 +351,11 @@ $('#users_list_table').on('click', 'td.edit-control > .edit-user-toggles > butto
|
|||
var row = users_list_table.row(tr);
|
||||
var rowData = row.data();
|
||||
|
||||
var index_delete = $.inArray(rowData['user_id'], users_to_delete);
|
||||
var index_purge = $.inArray(rowData['user_id'], users_to_purge);
|
||||
var index_delete = $.inArray(rowData['row_id'], users_to_delete);
|
||||
var index_purge = $.inArray(rowData['row_id'], users_to_purge);
|
||||
|
||||
if (index_purge === -1) {
|
||||
users_to_purge.push(rowData['user_id']);
|
||||
users_to_purge.push(rowData['row_id']);
|
||||
} else {
|
||||
users_to_purge.splice(index_purge, 1);
|
||||
if (index_delete != -1) {
|
||||
|
|
|
@ -116,14 +116,14 @@
|
|||
});
|
||||
|
||||
if (libraries_to_delete.length > 0) {
|
||||
$('#libraries-to-delete').prepend('<p>Are you REALLY sure you want to delete the following libraries:</p>')
|
||||
$('#libraries-to-delete').prepend('<p>Are you REALLY sure you want to delete the following libraries:</p>');
|
||||
for (var i = 0; i < libraries_to_delete.length; i++) {
|
||||
$('#libraries-to-delete').append('<li>' + $('div[data-id=' + libraries_to_delete[i] + ']').text() + '</li>');
|
||||
}
|
||||
}
|
||||
|
||||
if (libraries_to_purge.length > 0) {
|
||||
$('#libraries-to-purge').prepend('<p>Are you REALLY sure you want to purge all history for the following libraries:</p>')
|
||||
$('#libraries-to-purge').prepend('<p>Are you REALLY sure you want to purge all history for the following libraries:</p>');
|
||||
for (var i = 0; i < libraries_to_purge.length; i++) {
|
||||
$('#libraries-to-purge').append('<li>' + $('div[data-id=' + libraries_to_purge[i] + ']').text() + '</li>');
|
||||
}
|
||||
|
@ -131,11 +131,10 @@
|
|||
|
||||
$('#confirm-modal-delete').modal();
|
||||
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
|
||||
libraries_to_delete.forEach(function(row, idx) {
|
||||
$.ajax({
|
||||
url: 'delete_library',
|
||||
type: 'POST',
|
||||
data: { section_id: row },
|
||||
data: { row_ids: libraries_to_delete.join(',') },
|
||||
cache: false,
|
||||
async: true,
|
||||
success: function (data) {
|
||||
|
@ -143,12 +142,10 @@
|
|||
showMsg(msg, false, true, 2000);
|
||||
}
|
||||
});
|
||||
});
|
||||
libraries_to_purge.forEach(function(row, idx) {
|
||||
$.ajax({
|
||||
url: 'delete_all_library_history',
|
||||
type: 'POST',
|
||||
data: { section_id: row },
|
||||
data: { row_ids: libraries_to_purge.join(',') },
|
||||
cache: false,
|
||||
async: true,
|
||||
success: function (data) {
|
||||
|
@ -156,7 +153,6 @@
|
|||
showMsg(msg, false, true, 2000);
|
||||
}
|
||||
});
|
||||
});
|
||||
libraries_list_table.draw();
|
||||
});
|
||||
}
|
||||
|
@ -188,7 +184,7 @@
|
|||
complete: function (xhr, status) {
|
||||
var result = $.parseJSON(xhr.responseText);
|
||||
var msg = result.message;
|
||||
if (result.result == 'success') {
|
||||
if (result.result === 'success') {
|
||||
showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 2000, false);
|
||||
libraries_list_table.draw();
|
||||
} else {
|
||||
|
|
|
@ -62,9 +62,21 @@ DOCUMENTATION :: END
|
|||
<div class="table-card-back">
|
||||
<div class="user-info-wrapper">
|
||||
% if data['library_thumb'].startswith('http'):
|
||||
<div class="library-info-poster-face" style="background-image: url(${page('pms_image_proxy', data['library_thumb'], None, 80, 80)});"></div>
|
||||
<div class="library-info-poster-face" style="background-image: url(${page('pms_image_proxy', data['library_thumb'], None, 80, 80)});">
|
||||
% if not data['is_active']:
|
||||
<span class="inactive-library-tooltip" data-toggle="tooltip" title="Library not on Plex server">
|
||||
<i class="fa fa-2x fa-exclamation-triangle"></i>
|
||||
</span>
|
||||
% endif
|
||||
</div>
|
||||
% else:
|
||||
<div class="library-info-poster-face svg-icon library-${data['section_type']}"></div>
|
||||
<div class="library-info-poster-face svg-icon library-${data['section_type']}">
|
||||
% if not data['is_active']:
|
||||
<span class="inactive-library-tooltip" data-toggle="tooltip" title="Library not on Plex server">
|
||||
<i class="fa fa-2x fa-exclamation-triangle"></i>
|
||||
</span>
|
||||
% endif
|
||||
</div>
|
||||
% endif
|
||||
<div class="user-info-username">
|
||||
<span class="set-username">${data['section_name']}</span>
|
||||
|
@ -411,6 +423,8 @@ DOCUMENTATION :: END
|
|||
history_table.draw();
|
||||
});
|
||||
|
||||
$(".inactive-library-tooltip").tooltip();
|
||||
|
||||
% if _session['user_group'] == 'admin':
|
||||
function loadMediaInfoTable() {
|
||||
// Build media info table
|
||||
|
@ -471,18 +485,16 @@ DOCUMENTATION :: END
|
|||
$('#deleteCount').text(history_to_delete.length);
|
||||
$('#confirm-modal-delete').modal();
|
||||
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
|
||||
history_to_delete.forEach(function(row, idx) {
|
||||
$.ajax({
|
||||
url: 'delete_history_rows',
|
||||
type: 'POST',
|
||||
data: { row_id: row },
|
||||
data: { row_ids: history_to_delete.join(',') },
|
||||
async: true,
|
||||
success: function (data) {
|
||||
var msg = "History deleted";
|
||||
showMsg(msg, false, true, 2000);
|
||||
}
|
||||
});
|
||||
});
|
||||
history_table.draw();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -51,7 +51,13 @@ DOCUMENTATION :: END
|
|||
<div class="col-md-12">
|
||||
<div class="table-card-back">
|
||||
<div class="user-info-wrapper">
|
||||
<div class="user-info-poster-face" style="background-image: url(${data['user_thumb']});"></div>
|
||||
<div class="user-info-poster-face" style="background-image: url(${data['user_thumb']});">
|
||||
% if not data['is_active']:
|
||||
<span class="inactive-user-tooltip" data-toggle="tooltip" title="User not on Plex server">
|
||||
<i class="fa fa-2x fa-exclamation-triangle"></i>
|
||||
</span>
|
||||
% endif
|
||||
</div>
|
||||
<div class="user-info-username">
|
||||
<span class="set-username">${data['friendly_name']}</span>
|
||||
% if _session['user_group'] == 'admin':
|
||||
|
@ -540,6 +546,8 @@ DOCUMENTATION :: END
|
|||
login_log_table.draw();
|
||||
});
|
||||
|
||||
$(".inactive-user-tooltip").tooltip();
|
||||
|
||||
% if _session['user_group'] == 'admin':
|
||||
$("#edit-user-tooltip").tooltip();
|
||||
|
||||
|
@ -566,18 +574,16 @@ DOCUMENTATION :: END
|
|||
$('#deleteType').text('history');
|
||||
$('#confirm-modal-delete').modal();
|
||||
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
|
||||
history_to_delete.forEach(function(row, idx) {
|
||||
$.ajax({
|
||||
url: 'delete_history_rows',
|
||||
type: 'POST',
|
||||
data: { row_id: row },
|
||||
data: { row_ids: history_to_delete.join(',') },
|
||||
async: true,
|
||||
success: function (data) {
|
||||
var msg = "History deleted";
|
||||
showMsg(msg, false, true, 2000);
|
||||
}
|
||||
});
|
||||
});
|
||||
history_table.draw();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -119,14 +119,14 @@
|
|||
});
|
||||
|
||||
if (users_to_delete.length > 0) {
|
||||
$('#users-to-delete').prepend('<p>Are you REALLY sure you want to delete and purge all history for the following users:</p>')
|
||||
$('#users-to-delete').prepend('<p>Are you REALLY sure you want to delete and purge all history for the following users:</p>');
|
||||
for (var i = 0; i < users_to_delete.length; i++) {
|
||||
$('#users-to-delete').append('<li>' + $('div[data-id=' + users_to_delete[i] + '] > input').val() + '</li>');
|
||||
}
|
||||
}
|
||||
|
||||
if (users_to_purge.length > 0) {
|
||||
$('#users-to-purge').prepend('<p>Are you REALLY sure you want to purge all history for the following users:</p>')
|
||||
$('#users-to-purge').prepend('<p>Are you REALLY sure you want to purge all history for the following users:</p>');
|
||||
for (var i = 0; i < users_to_purge.length; i++) {
|
||||
$('#users-to-purge').append('<li>' + $('div[data-id=' + users_to_purge[i] + '] > input').val() + '</li>');
|
||||
}
|
||||
|
@ -134,11 +134,10 @@
|
|||
|
||||
$('#confirm-modal-delete').modal();
|
||||
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
|
||||
users_to_delete.forEach(function(row, idx) {
|
||||
$.ajax({
|
||||
url: 'delete_user',
|
||||
type: 'POST',
|
||||
data: { user_id: row },
|
||||
data: { row_ids: users_to_delete.join(',') },
|
||||
cache: false,
|
||||
async: true,
|
||||
success: function (data) {
|
||||
|
@ -146,12 +145,10 @@
|
|||
showMsg(msg, false, true, 2000);
|
||||
}
|
||||
});
|
||||
});
|
||||
users_to_purge.forEach(function(row, idx) {
|
||||
$.ajax({
|
||||
url: 'delete_all_user_history',
|
||||
type: 'POST',
|
||||
data: { user_id: row },
|
||||
data: { row_ids: users_to_purge.join(',') },
|
||||
cache: false,
|
||||
async: true,
|
||||
success: function (data) {
|
||||
|
@ -159,7 +156,6 @@
|
|||
showMsg(msg, false, true, 2000);
|
||||
}
|
||||
});
|
||||
});
|
||||
users_list_table.draw();
|
||||
});
|
||||
}
|
||||
|
@ -192,7 +188,7 @@
|
|||
complete: function (xhr, status) {
|
||||
var result = $.parseJSON(xhr.responseText);
|
||||
var msg = result.message;
|
||||
if (result.result == 'success') {
|
||||
if (result.result === 'success') {
|
||||
showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 2000, false);
|
||||
users_list_table.draw();
|
||||
} else {
|
||||
|
|
|
@ -693,11 +693,11 @@ def dbcheck():
|
|||
c_db.execute(
|
||||
'CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, '
|
||||
'user_id INTEGER DEFAULT NULL UNIQUE, username TEXT NOT NULL, friendly_name TEXT, '
|
||||
'thumb TEXT, custom_avatar_url TEXT, email TEXT, is_admin INTEGER DEFAULT 0, is_home_user INTEGER DEFAULT NULL, '
|
||||
'is_allow_sync INTEGER DEFAULT NULL, is_restricted INTEGER DEFAULT NULL, do_notify INTEGER DEFAULT 1, '
|
||||
'keep_history INTEGER DEFAULT 1, deleted_user INTEGER DEFAULT 0, allow_guest INTEGER DEFAULT 0, '
|
||||
'user_token TEXT, server_token TEXT, shared_libraries TEXT, filter_all TEXT, filter_movies TEXT, filter_tv TEXT, '
|
||||
'filter_music TEXT, filter_photos TEXT)'
|
||||
'thumb TEXT, custom_avatar_url TEXT, email TEXT, is_active INTEGER DEFAULT 1, is_admin INTEGER DEFAULT 0, '
|
||||
'is_home_user INTEGER DEFAULT NULL, is_allow_sync INTEGER DEFAULT NULL, is_restricted INTEGER DEFAULT NULL, '
|
||||
'do_notify INTEGER DEFAULT 1, keep_history INTEGER DEFAULT 1, deleted_user INTEGER DEFAULT 0, '
|
||||
'allow_guest INTEGER DEFAULT 0, user_token TEXT, server_token TEXT, shared_libraries TEXT, '
|
||||
'filter_all TEXT, filter_movies TEXT, filter_tv TEXT, filter_music TEXT, filter_photos TEXT)'
|
||||
)
|
||||
|
||||
# library_sections table :: This table keeps record of the servers library sections
|
||||
|
@ -705,7 +705,7 @@ def dbcheck():
|
|||
'CREATE TABLE IF NOT EXISTS library_sections (id INTEGER PRIMARY KEY AUTOINCREMENT, '
|
||||
'server_id TEXT, section_id INTEGER, section_name TEXT, section_type TEXT, agent TEXT, '
|
||||
'thumb TEXT, custom_thumb_url TEXT, art TEXT, custom_art_url TEXT, '
|
||||
'count INTEGER, parent_count INTEGER, child_count INTEGER, '
|
||||
'count INTEGER, parent_count INTEGER, child_count INTEGER, is_active INTEGER DEFAULT 1, '
|
||||
'do_notify INTEGER DEFAULT 1, do_notify_created INTEGER DEFAULT 1, keep_history INTEGER DEFAULT 1, '
|
||||
'deleted_section INTEGER DEFAULT 0, UNIQUE(server_id, section_id))'
|
||||
)
|
||||
|
@ -726,16 +726,19 @@ def dbcheck():
|
|||
'on_created INTEGER DEFAULT 0, on_extdown INTEGER DEFAULT 0, on_intdown INTEGER DEFAULT 0, '
|
||||
'on_extup INTEGER DEFAULT 0, on_intup INTEGER DEFAULT 0, on_pmsupdate INTEGER DEFAULT 0, '
|
||||
'on_concurrent INTEGER DEFAULT 0, on_newdevice INTEGER DEFAULT 0, on_plexpyupdate INTEGER DEFAULT 0, '
|
||||
'on_plexpydbcorrupt INTEGER DEFAULT 0, '
|
||||
'on_play_subject TEXT, on_stop_subject TEXT, on_pause_subject TEXT, '
|
||||
'on_resume_subject TEXT, on_change_subject TEXT, on_buffer_subject TEXT, on_watched_subject TEXT, '
|
||||
'on_created_subject TEXT, on_extdown_subject TEXT, on_intdown_subject TEXT, '
|
||||
'on_extup_subject TEXT, on_intup_subject TEXT, on_pmsupdate_subject TEXT, '
|
||||
'on_concurrent_subject TEXT, on_newdevice_subject TEXT, on_plexpyupdate_subject TEXT, '
|
||||
'on_plexpydbcorrupt_subject TEXT, '
|
||||
'on_play_body TEXT, on_stop_body TEXT, on_pause_body TEXT, '
|
||||
'on_resume_body TEXT, on_change_body TEXT, on_buffer_body TEXT, on_watched_body TEXT, '
|
||||
'on_created_body TEXT, on_extdown_body TEXT, on_intdown_body TEXT, '
|
||||
'on_extup_body TEXT, on_intup_body TEXT, on_pmsupdate_body TEXT, '
|
||||
'on_concurrent_body TEXT, on_newdevice_body TEXT, on_plexpyupdate_body TEXT, '
|
||||
'on_plexpydbcorrupt_body TEXT, '
|
||||
'custom_conditions TEXT, custom_conditions_logic TEXT)'
|
||||
)
|
||||
|
||||
|
@ -1247,7 +1250,7 @@ def dbcheck():
|
|||
try:
|
||||
c_db.execute('SELECT video_dynamic_range FROM sessions')
|
||||
except sqlite3.OperationalError:
|
||||
logger.debug(u"Altering database. Updating database table sessions.")
|
||||
logger.debug("Altering database. Updating database table sessions.")
|
||||
c_db.execute(
|
||||
'ALTER TABLE sessions ADD COLUMN video_dynamic_range TEXT'
|
||||
)
|
||||
|
@ -1259,7 +1262,7 @@ def dbcheck():
|
|||
try:
|
||||
c_db.execute('SELECT channel_identifier FROM sessions')
|
||||
except sqlite3.OperationalError:
|
||||
logger.debug(u"Altering database. Updating database table sessions.")
|
||||
logger.debug("Altering database. Updating database table sessions.")
|
||||
c_db.execute(
|
||||
'ALTER TABLE sessions ADD COLUMN channel_call_sign TEXT'
|
||||
)
|
||||
|
@ -1274,7 +1277,7 @@ def dbcheck():
|
|||
try:
|
||||
c_db.execute('SELECT originally_available_at FROM sessions')
|
||||
except sqlite3.OperationalError:
|
||||
logger.debug(u"Altering database. Updating database table sessions.")
|
||||
logger.debug("Altering database. Updating database table sessions.")
|
||||
c_db.execute(
|
||||
'ALTER TABLE sessions ADD COLUMN originally_available_at TEXT'
|
||||
)
|
||||
|
@ -1286,7 +1289,7 @@ def dbcheck():
|
|||
try:
|
||||
c_db.execute('SELECT guid FROM sessions')
|
||||
except sqlite3.OperationalError:
|
||||
logger.debug(u"Altering database. Updating database table sessions.")
|
||||
logger.debug("Altering database. Updating database table sessions.")
|
||||
c_db.execute(
|
||||
'ALTER TABLE sessions ADD COLUMN guid TEXT'
|
||||
)
|
||||
|
@ -1358,12 +1361,12 @@ def dbcheck():
|
|||
result = c_db.execute('SELECT platform FROM session_history '
|
||||
'WHERE platform = "windows"').fetchall()
|
||||
if len(result) > 0:
|
||||
logger.debug(u"Altering database. Capitalizing Windows platform values in session_history table.")
|
||||
logger.debug("Altering database. Capitalizing Windows platform values in session_history table.")
|
||||
c_db.execute(
|
||||
'UPDATE session_history SET platform = "Windows" WHERE platform = "windows" '
|
||||
)
|
||||
except sqlite3.OperationalError:
|
||||
logger.warn(u"Unable to capitalize Windows platform values in session_history table.")
|
||||
logger.warn("Unable to capitalize Windows platform values in session_history table.")
|
||||
|
||||
# Upgrade session_history_metadata table from earlier versions
|
||||
try:
|
||||
|
@ -1414,7 +1417,7 @@ def dbcheck():
|
|||
try:
|
||||
c_db.execute('SELECT live FROM session_history_metadata')
|
||||
except sqlite3.OperationalError:
|
||||
logger.debug(u"Altering database. Updating database table session_history_metadata.")
|
||||
logger.debug("Altering database. Updating database table session_history_metadata.")
|
||||
c_db.execute(
|
||||
'ALTER TABLE session_history_metadata ADD COLUMN live INTEGER DEFAULT 0'
|
||||
)
|
||||
|
@ -1663,7 +1666,7 @@ def dbcheck():
|
|||
try:
|
||||
c_db.execute('SELECT video_dynamic_range FROM session_history_media_info')
|
||||
except sqlite3.OperationalError:
|
||||
logger.debug(u"Altering database. Updating database table session_history_media_info.")
|
||||
logger.debug("Altering database. Updating database table session_history_media_info.")
|
||||
c_db.execute(
|
||||
'ALTER TABLE session_history_media_info ADD COLUMN video_dynamic_range TEXT '
|
||||
)
|
||||
|
@ -1763,6 +1766,15 @@ def dbcheck():
|
|||
'ALTER TABLE users ADD COLUMN is_admin INTEGER DEFAULT 0'
|
||||
)
|
||||
|
||||
# Upgrade users table from earlier versions
|
||||
try:
|
||||
c_db.execute('SELECT is_active FROM users')
|
||||
except sqlite3.OperationalError:
|
||||
logger.debug("Altering database. Updating database table users.")
|
||||
c_db.execute(
|
||||
'ALTER TABLE users ADD COLUMN is_active INTEGER DEFAULT 1'
|
||||
)
|
||||
|
||||
# Upgrade notify_log table from earlier versions
|
||||
try:
|
||||
c_db.execute('SELECT poster_url FROM notify_log')
|
||||
|
@ -1930,11 +1942,20 @@ def dbcheck():
|
|||
try:
|
||||
c_db.execute('SELECT custom_art_url FROM library_sections')
|
||||
except sqlite3.OperationalError:
|
||||
logger.debug(u"Altering database. Updating database table library_sections.")
|
||||
logger.debug("Altering database. Updating database table library_sections.")
|
||||
c_db.execute(
|
||||
'ALTER TABLE library_sections ADD COLUMN custom_art_url TEXT'
|
||||
)
|
||||
|
||||
# Upgrade library_sections table from earlier versions
|
||||
try:
|
||||
c_db.execute('SELECT is_active FROM library_sections')
|
||||
except sqlite3.OperationalError:
|
||||
logger.debug("Altering database. Updating database table library_sections.")
|
||||
c_db.execute(
|
||||
'ALTER TABLE library_sections ADD COLUMN is_active INTEGER DEFAULT 1'
|
||||
)
|
||||
|
||||
# Upgrade users table from earlier versions (remove UNIQUE constraint on username)
|
||||
try:
|
||||
result = c_db.execute('SELECT SQL FROM sqlite_master WHERE type="table" AND name="users"').fetchone()
|
||||
|
@ -2021,6 +2042,21 @@ def dbcheck():
|
|||
'ALTER TABLE notifiers ADD COLUMN on_change_body TEXT'
|
||||
)
|
||||
|
||||
# Upgrade notifiers table from earlier versions
|
||||
try:
|
||||
c_db.execute('SELECT on_plexpydbcorrupt FROM notifiers')
|
||||
except sqlite3.OperationalError:
|
||||
logger.debug("Altering database. Updating database table notifiers.")
|
||||
c_db.execute(
|
||||
'ALTER TABLE notifiers ADD COLUMN on_plexpydbcorrupt INTEGER DEFAULT 0'
|
||||
)
|
||||
c_db.execute(
|
||||
'ALTER TABLE notifiers ADD COLUMN on_plexpydbcorrupt_subject TEXT'
|
||||
)
|
||||
c_db.execute(
|
||||
'ALTER TABLE notifiers ADD COLUMN on_plexpydbcorrupt_body TEXT'
|
||||
)
|
||||
|
||||
# Upgrade tvmaze_lookup table from earlier versions
|
||||
try:
|
||||
c_db.execute('SELECT rating_key FROM tvmaze_lookup')
|
||||
|
@ -2157,11 +2193,11 @@ def shutdown(restart=False, update=False, checkout=False, reset=False):
|
|||
logger.warn("Tautulli failed to switch git branch: %s. Restarting." % e)
|
||||
|
||||
if reset:
|
||||
logger.info(u"Tautulli is resetting the git install...")
|
||||
logger.info("Tautulli is resetting the git install...")
|
||||
try:
|
||||
versioncheck.reset_git_install()
|
||||
except Exception as e:
|
||||
logger.warn(u"Tautulli failed to reset git install: %s. Restarting." % e)
|
||||
logger.warn("Tautulli failed to reset git install: %s. Restarting." % e)
|
||||
|
||||
if CREATEPID:
|
||||
logger.info("Removing pidfile %s", PIDFILE)
|
||||
|
|
|
@ -308,7 +308,7 @@ class API2(object):
|
|||
def sql(self, query=''):
|
||||
""" Query the Tautulli database with raw SQL. Automatically makes a backup of
|
||||
the database if the latest backup is older then 24h. `api_sql` must be
|
||||
manually enabled in the config file.
|
||||
manually enabled in the config file while Tautulli is shut down.
|
||||
|
||||
```
|
||||
Required parameters:
|
||||
|
|
|
@ -25,8 +25,10 @@ import time
|
|||
|
||||
import plexpy
|
||||
if plexpy.PYTHON2:
|
||||
import helpers
|
||||
import logger
|
||||
else:
|
||||
from plexpy import helpers
|
||||
from plexpy import logger
|
||||
|
||||
|
||||
|
@ -64,6 +66,53 @@ def delete_recently_added():
|
|||
return clear_table('recently_added')
|
||||
|
||||
|
||||
def delete_rows_from_table(table, row_ids):
|
||||
if row_ids and isinstance(row_ids, basestring):
|
||||
row_ids = map(helpers.cast_to_int, row_ids.split(','))
|
||||
|
||||
logger.info("Tautulli Database :: Deleting row ids %s from %s database table", row_ids, table)
|
||||
query = "DELETE FROM " + table + " WHERE id IN (%s) " % ','.join(['?'] * len(row_ids))
|
||||
monitor_db = MonitorDatabase()
|
||||
monitor_db.action(query, row_ids)
|
||||
|
||||
|
||||
def delete_session_history_rows(row_ids=None):
|
||||
if row_ids:
|
||||
for table in ('session_history', 'session_history_media_info', 'session_history_metadata'):
|
||||
delete_rows_from_table(table=table, row_ids=row_ids)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def delete_user_history(user_id=None):
|
||||
if str(user_id).isdigit():
|
||||
monitor_db = MonitorDatabase()
|
||||
|
||||
# Get all history associated with the user_id
|
||||
result = monitor_db.select('SELECT id FROM session_history WHERE user_id = ?',
|
||||
[user_id])
|
||||
row_ids = [row['id'] for row in result]
|
||||
|
||||
logger.info("Tautulli Database :: Deleting all history for user_id %s from database." % user_id)
|
||||
return delete_session_history_rows(row_ids=row_ids)
|
||||
|
||||
|
||||
def delete_library_history(server_id=None, section_id=None):
|
||||
if server_id and str(section_id).isdigit():
|
||||
monitor_db = MonitorDatabase()
|
||||
|
||||
# Get all history associated with the server_id and section_id
|
||||
result = monitor_db.select('SELECT session_history.id FROM session_history '
|
||||
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id '
|
||||
'WHERE session_history.server_id = ? AND session_history_metadata.section_id = ?',
|
||||
[server_id, section_id])
|
||||
row_ids = [row['id'] for row in result]
|
||||
|
||||
logger.info("Tautulli Database :: Deleting all history for library server_id %s and section_id %s from database."
|
||||
% (server_id, section_id))
|
||||
return delete_session_history_rows(row_ids=row_ids)
|
||||
|
||||
|
||||
def db_filename(filename=FILENAME):
|
||||
""" Returns the filepath to the db """
|
||||
|
||||
|
@ -79,6 +128,7 @@ def make_backup(cleanup=False, scheduler=False):
|
|||
corrupt = ''
|
||||
if not integrity:
|
||||
corrupt = '.corrupt'
|
||||
plexpy.NOTIFY_QUEUE.put({'notify_action': 'on_plexpydbcorrupt'})
|
||||
|
||||
if scheduler:
|
||||
backup_file = 'tautulli.backup-{}{}.sched.db'.format(arrow.now().format('YYYYMMDDHHmmss'), corrupt)
|
||||
|
|
|
@ -82,7 +82,7 @@ class DataFactory(object):
|
|||
|
||||
columns = [
|
||||
'session_history.reference_id',
|
||||
'session_history.id',
|
||||
'session_history.id AS row_id',
|
||||
'MAX(started) AS date',
|
||||
'MIN(started) AS started',
|
||||
'MAX(stopped) AS stopped',
|
||||
|
@ -134,7 +134,7 @@ class DataFactory(object):
|
|||
|
||||
columns_union = [
|
||||
'NULL AS reference_id',
|
||||
'NULL AS id',
|
||||
'NULL AS row_id',
|
||||
'started AS date',
|
||||
'started',
|
||||
'stopped',
|
||||
|
@ -246,7 +246,7 @@ class DataFactory(object):
|
|||
platform = common.PLATFORM_NAME_OVERRIDES.get(item['platform'], item['platform'])
|
||||
|
||||
row = {'reference_id': item['reference_id'],
|
||||
'id': item['id'],
|
||||
'row_id': item['row_id'],
|
||||
'date': item['date'],
|
||||
'started': item['started'],
|
||||
'stopped': item['stopped'],
|
||||
|
@ -1425,7 +1425,7 @@ class DataFactory(object):
|
|||
|
||||
def delete_lookup_info(self, rating_key='', service='', delete_all=False):
|
||||
if not rating_key and not delete_all:
|
||||
logger.error(u"Tautulli DataFactory :: Unable to delete lookup info: rating_key not provided.")
|
||||
logger.error("Tautulli DataFactory :: Unable to delete lookup info: rating_key not provided.")
|
||||
return False
|
||||
|
||||
monitor_db = database.MonitorDatabase()
|
||||
|
@ -1439,12 +1439,12 @@ class DataFactory(object):
|
|||
return bool(result_themoviedb or result_tvmaze or result_musicbrainz)
|
||||
elif service and delete_all:
|
||||
if service.lower() in ('themoviedb', 'tvmaze', 'musicbrainz'):
|
||||
logger.info(u"Tautulli DataFactory :: Deleting all lookup info for '%s' from the database."
|
||||
logger.info("Tautulli DataFactory :: Deleting all lookup info for '%s' from the database."
|
||||
% service)
|
||||
result = monitor_db.action('DELETE FROM %s_lookup' % service.lower())
|
||||
return bool(result)
|
||||
else:
|
||||
logger.error(u"Tautulli DataFactory :: Unable to delete lookup info: invalid service '%s' provided."
|
||||
logger.error("Tautulli DataFactory :: Unable to delete lookup info: invalid service '%s' provided."
|
||||
% service)
|
||||
|
||||
def get_search_query(self, rating_key=''):
|
||||
|
@ -1579,22 +1579,6 @@ class DataFactory(object):
|
|||
|
||||
return key_list
|
||||
|
||||
def delete_session_history_rows(self, row_id=None):
|
||||
monitor_db = database.MonitorDatabase()
|
||||
|
||||
if row_id.isdigit():
|
||||
logger.info("Tautulli DataFactory :: Deleting row id %s from the session history database." % row_id)
|
||||
session_history_del = \
|
||||
monitor_db.action('DELETE FROM session_history WHERE id = ?', [row_id])
|
||||
session_history_media_info_del = \
|
||||
monitor_db.action('DELETE FROM session_history_media_info WHERE id = ?', [row_id])
|
||||
session_history_metadata_del = \
|
||||
monitor_db.action('DELETE FROM session_history_metadata WHERE id = ?', [row_id])
|
||||
|
||||
return 'Deleted rows %s.' % row_id
|
||||
else:
|
||||
return 'Unable to delete rows. Input row not valid.'
|
||||
|
||||
def update_metadata(self, old_key_list='', new_key_list='', media_type=''):
|
||||
pms_connect = pmsconnect.PmsConnect()
|
||||
monitor_db = database.MonitorDatabase()
|
||||
|
|
|
@ -631,17 +631,17 @@ def is_valid_ip(address):
|
|||
|
||||
def update_geoip_db():
|
||||
if plexpy.CONFIG.GEOIP_DB_INSTALLED:
|
||||
logger.info(u"Tautulli Helpers :: Checking for GeoLite2 database updates.")
|
||||
logger.info("Tautulli Helpers :: Checking for GeoLite2 database updates.")
|
||||
now = timestamp()
|
||||
if now - plexpy.CONFIG.GEOIP_DB_INSTALLED >= plexpy.CONFIG.GEOIP_DB_UPDATE_DAYS * 24 * 60 * 60:
|
||||
return install_geoip_db(update=True)
|
||||
logger.info(u"Tautulli Helpers :: GeoLite2 database already updated within the last %s days."
|
||||
logger.info("Tautulli Helpers :: GeoLite2 database already updated within the last %s days."
|
||||
% plexpy.CONFIG.GEOIP_DB_UPDATE_DAYS)
|
||||
|
||||
|
||||
def install_geoip_db(update=False):
|
||||
if not plexpy.CONFIG.MAXMIND_LICENSE_KEY:
|
||||
logger.error(u"Tautulli Helpers :: Failed to download GeoLite2 database file from MaxMind: Missing MaxMindLicense Key")
|
||||
logger.error("Tautulli Helpers :: Failed to download GeoLite2 database file from MaxMind: Missing MaxMindLicense Key")
|
||||
return False
|
||||
|
||||
maxmind_db = 'GeoLite2-City'
|
||||
|
@ -674,7 +674,7 @@ def install_geoip_db(update=False):
|
|||
return False
|
||||
|
||||
# Check MD5 hash for GeoLite2 tar.gz file
|
||||
logger.debug(u"Tautulli Helpers :: Checking MD5 checksum for GeoLite2 gzip file...")
|
||||
logger.debug("Tautulli Helpers :: Checking MD5 checksum for GeoLite2 gzip file...")
|
||||
try:
|
||||
hash_md5 = hashlib.md5()
|
||||
with open(temp_gz, 'rb') as f:
|
||||
|
@ -690,11 +690,11 @@ def install_geoip_db(update=False):
|
|||
"Checksum: %s, file hash: %s" % (md5_checksum, md5_hash))
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(u"Tautulli Helpers :: Failed to generate MD5 checksum for GeoLite2 gzip file: %s" % e)
|
||||
logger.error("Tautulli Helpers :: Failed to generate MD5 checksum for GeoLite2 gzip file: %s" % e)
|
||||
return False
|
||||
|
||||
# Extract the GeoLite2 database file
|
||||
logger.debug(u"Tautulli Helpers :: Extracting GeoLite2 database...")
|
||||
logger.debug("Tautulli Helpers :: Extracting GeoLite2 database...")
|
||||
try:
|
||||
mmdb = None
|
||||
with tarfile.open(temp_gz, 'r:gz') as tar:
|
||||
|
@ -707,7 +707,7 @@ def install_geoip_db(update=False):
|
|||
if not mmdb:
|
||||
raise Exception("{} not found in gzip file.".format(geolite2_db))
|
||||
except Exception as e:
|
||||
logger.error(u"Tautulli Helpers :: Failed to extract the GeoLite2 database: %s" % e)
|
||||
logger.error("Tautulli Helpers :: Failed to extract the GeoLite2 database: %s" % e)
|
||||
return False
|
||||
|
||||
# Delete temportary GeoLite2 gzip file
|
||||
|
@ -722,7 +722,7 @@ def install_geoip_db(update=False):
|
|||
plexpy.CONFIG.__setattr__('GEOIP_DB_INSTALLED', timestamp())
|
||||
plexpy.CONFIG.write()
|
||||
|
||||
logger.debug(u"Tautulli Helpers :: GeoLite2 database installed successfully.")
|
||||
logger.debug("Tautulli Helpers :: GeoLite2 database installed successfully.")
|
||||
|
||||
if not update:
|
||||
plexpy.schedule_job(update_geoip_db, 'Update GeoLite2 database', hours=12, minutes=0, seconds=0)
|
||||
|
@ -741,7 +741,7 @@ def uninstall_geoip_db():
|
|||
plexpy.CONFIG.__setattr__('GEOIP_DB_INSTALLED', 0)
|
||||
plexpy.CONFIG.write()
|
||||
|
||||
logger.debug(u"Tautulli Helpers :: GeoLite2 database uninstalled successfully.")
|
||||
logger.debug("Tautulli Helpers :: GeoLite2 database uninstalled successfully.")
|
||||
|
||||
plexpy.schedule_job(update_geoip_db, 'Update GeoLite2 database', hours=0, minutes=0, seconds=0)
|
||||
|
||||
|
|
|
@ -60,7 +60,12 @@ def refresh_libraries():
|
|||
library_keys = []
|
||||
new_keys = []
|
||||
|
||||
# Keep track of section_id to update is_active status
|
||||
section_ids = [common.LIVE_TV_SECTION_ID] # Live TV library always considered active
|
||||
|
||||
for section in library_sections:
|
||||
section_ids.append(helpers.cast_to_int(section['section_id']))
|
||||
|
||||
section_keys = {'server_id': server_id,
|
||||
'section_id': section['section_id']}
|
||||
section_values = {'server_id': server_id,
|
||||
|
@ -82,6 +87,10 @@ def refresh_libraries():
|
|||
if result == 'insert':
|
||||
new_keys.append(section['section_id'])
|
||||
|
||||
query = 'UPDATE library_sections SET is_active = 0 WHERE server_id != ? OR ' \
|
||||
'section_id NOT IN ({})'.format(', '.join(['?'] * len(section_ids)))
|
||||
monitor_db.action(query=query, args=[plexpy.CONFIG.PMS_IDENTIFIER] + section_ids)
|
||||
|
||||
if plexpy.CONFIG.HOME_LIBRARY_CARDS == ['first_run_wizard']:
|
||||
plexpy.CONFIG.__setattr__('HOME_LIBRARY_CARDS', library_keys)
|
||||
plexpy.CONFIG.write()
|
||||
|
@ -109,7 +118,7 @@ def add_live_tv_library():
|
|||
if not plexpy.CONFIG.ADD_LIVE_TV_LIBRARY:
|
||||
return
|
||||
|
||||
logger.info(u"Tautulli Libraries :: Adding Live TV library to the database.")
|
||||
logger.info("Tautulli Libraries :: Adding Live TV library to the database.")
|
||||
|
||||
monitor_db = database.MonitorDatabase()
|
||||
|
||||
|
@ -306,7 +315,9 @@ class Libraries(object):
|
|||
|
||||
group_by = 'session_history.reference_id' if grouping else 'session_history.id'
|
||||
|
||||
columns = ['library_sections.section_id',
|
||||
columns = ['library_sections.id AS row_id',
|
||||
'library_sections.server_id',
|
||||
'library_sections.section_id',
|
||||
'library_sections.section_name',
|
||||
'library_sections.section_type',
|
||||
'library_sections.count',
|
||||
|
@ -320,7 +331,7 @@ class Libraries(object):
|
|||
ELSE 0 END) - SUM(CASE WHEN session_history.paused_counter IS NULL THEN 0 ELSE \
|
||||
session_history.paused_counter END) AS duration',
|
||||
'MAX(session_history.started) AS last_accessed',
|
||||
'MAX(session_history.id) AS id',
|
||||
'MAX(session_history.id) AS history_row_id',
|
||||
'session_history_metadata.full_title AS last_played',
|
||||
'session_history.rating_key',
|
||||
'session_history_metadata.media_type',
|
||||
|
@ -339,7 +350,8 @@ class Libraries(object):
|
|||
'session_history_metadata.guid',
|
||||
'library_sections.do_notify',
|
||||
'library_sections.do_notify_created',
|
||||
'library_sections.keep_history'
|
||||
'library_sections.keep_history',
|
||||
'library_sections.is_active'
|
||||
]
|
||||
try:
|
||||
query = data_tables.ssp_query(table_name='library_sections',
|
||||
|
@ -378,7 +390,9 @@ class Libraries(object):
|
|||
else:
|
||||
library_thumb = common.DEFAULT_COVER_THUMB
|
||||
|
||||
row = {'section_id': item['section_id'],
|
||||
row = {'row_id': item['row_id'],
|
||||
'server_id': item['server_id'],
|
||||
'section_id': item['section_id'],
|
||||
'section_name': item['section_name'],
|
||||
'section_type': item['section_type'],
|
||||
'count': item['count'],
|
||||
|
@ -389,7 +403,7 @@ class Libraries(object):
|
|||
'plays': item['plays'],
|
||||
'duration': item['duration'],
|
||||
'last_accessed': item['last_accessed'],
|
||||
'id': item['id'],
|
||||
'history_row_id': item['history_row_id'],
|
||||
'last_played': item['last_played'],
|
||||
'rating_key': item['rating_key'],
|
||||
'media_type': item['media_type'],
|
||||
|
@ -405,7 +419,8 @@ class Libraries(object):
|
|||
'guid': item['guid'],
|
||||
'do_notify': helpers.checked(item['do_notify']),
|
||||
'do_notify_created': helpers.checked(item['do_notify_created']),
|
||||
'keep_history': helpers.checked(item['keep_history'])
|
||||
'keep_history': helpers.checked(item['keep_history']),
|
||||
'is_active': item['is_active']
|
||||
}
|
||||
|
||||
rows.append(row)
|
||||
|
@ -741,7 +756,9 @@ class Libraries(object):
|
|||
logger.warn("Tautulli Libraries :: Unable to execute database query for set_config: %s." % e)
|
||||
|
||||
def get_details(self, section_id=None):
|
||||
default_return = {'section_id': 0,
|
||||
default_return = {'row_id': 0,
|
||||
'server_id': '',
|
||||
'section_id': 0,
|
||||
'section_name': 'Local',
|
||||
'section_type': '',
|
||||
'library_thumb': common.DEFAULT_COVER_THUMB,
|
||||
|
@ -749,6 +766,7 @@ class Libraries(object):
|
|||
'count': 0,
|
||||
'parent_count': 0,
|
||||
'child_count': 0,
|
||||
'is_active': 1,
|
||||
'do_notify': 0,
|
||||
'do_notify_created': 0,
|
||||
'keep_history': 1,
|
||||
|
@ -763,9 +781,10 @@ class Libraries(object):
|
|||
|
||||
try:
|
||||
if str(section_id).isdigit():
|
||||
query = 'SELECT section_id, section_name, section_type, count, parent_count, child_count, ' \
|
||||
query = 'SELECT id AS row_id, server_id, section_id, section_name, section_type, ' \
|
||||
'count, parent_count, child_count, ' \
|
||||
'thumb AS library_thumb, custom_thumb_url AS custom_thumb, art AS library_art, ' \
|
||||
'custom_art_url AS custom_art, ' \
|
||||
'custom_art_url AS custom_art, is_active, ' \
|
||||
'do_notify, do_notify_created, keep_history, deleted_section ' \
|
||||
'FROM library_sections ' \
|
||||
'WHERE section_id = ? '
|
||||
|
@ -791,7 +810,9 @@ class Libraries(object):
|
|||
else:
|
||||
library_art = item['library_art']
|
||||
|
||||
library_details = {'section_id': item['section_id'],
|
||||
library_details = {'row_id': item['row_id'],
|
||||
'server_id': item['server_id'],
|
||||
'section_id': item['section_id'],
|
||||
'section_name': item['section_name'],
|
||||
'section_type': item['section_type'],
|
||||
'library_thumb': library_thumb,
|
||||
|
@ -799,6 +820,7 @@ class Libraries(object):
|
|||
'count': item['count'],
|
||||
'parent_count': item['parent_count'],
|
||||
'child_count': item['child_count'],
|
||||
'is_active': item['is_active'],
|
||||
'do_notify': item['do_notify'],
|
||||
'do_notify_created': item['do_notify_created'],
|
||||
'keep_history': item['keep_history'],
|
||||
|
@ -828,21 +850,25 @@ class Libraries(object):
|
|||
# If there is no library data we must return something
|
||||
return default_return
|
||||
|
||||
def get_watch_time_stats(self, section_id=None, grouping=None):
|
||||
def get_watch_time_stats(self, section_id=None, grouping=None, query_days=None):
|
||||
if not session.allow_session_library(section_id):
|
||||
return []
|
||||
|
||||
if grouping is None:
|
||||
grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES
|
||||
|
||||
if query_days and query_days is not None:
|
||||
query_days = map(helpers.cast_to_int, query_days.split(','))
|
||||
else:
|
||||
query_days = [1, 7, 30, 0]
|
||||
|
||||
monitor_db = database.MonitorDatabase()
|
||||
|
||||
time_queries = [1, 7, 30, 0]
|
||||
library_watch_time_stats = []
|
||||
|
||||
group_by = 'session_history.reference_id' if grouping else 'session_history.id'
|
||||
|
||||
for days in time_queries:
|
||||
for days in query_days:
|
||||
try:
|
||||
if days > 0:
|
||||
if str(section_id).isdigit():
|
||||
|
@ -1014,61 +1040,41 @@ class Libraries(object):
|
|||
|
||||
return libraries
|
||||
|
||||
def delete_all_history(self, section_id=None):
|
||||
def delete(self, server_id=None, section_id=None, row_ids=None, purge_only=False):
|
||||
monitor_db = database.MonitorDatabase()
|
||||
|
||||
try:
|
||||
if section_id.isdigit():
|
||||
logger.info("Tautulli Libraries :: Deleting all history for library id %s from database." % section_id)
|
||||
session_history_media_info_del = \
|
||||
monitor_db.action('DELETE FROM '
|
||||
'session_history_media_info '
|
||||
'WHERE session_history_media_info.id IN (SELECT session_history_media_info.id '
|
||||
'FROM session_history_media_info '
|
||||
'JOIN session_history_metadata ON session_history_media_info.id = session_history_metadata.id '
|
||||
'WHERE session_history_metadata.section_id = ?)', [section_id])
|
||||
session_history_del = \
|
||||
monitor_db.action('DELETE FROM '
|
||||
'session_history '
|
||||
'WHERE session_history.id IN (SELECT session_history.id '
|
||||
'FROM session_history '
|
||||
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id '
|
||||
'WHERE session_history_metadata.section_id = ?)', [section_id])
|
||||
session_history_metadata_del = \
|
||||
monitor_db.action('DELETE FROM '
|
||||
'session_history_metadata '
|
||||
'WHERE session_history_metadata.section_id = ?', [section_id])
|
||||
if row_ids and row_ids is not None:
|
||||
row_ids = map(helpers.cast_to_int, row_ids.split(','))
|
||||
|
||||
return 'Deleted all items for section_id %s.' % section_id
|
||||
# Get the user_ids corresponding to the row_ids
|
||||
result = monitor_db.select('SELECT server_id, section_id FROM library_sections '
|
||||
'WHERE id IN ({})'.format(','.join(['?'] * len(row_ids))), row_ids)
|
||||
|
||||
success = []
|
||||
for library in result:
|
||||
success.append(self.delete(server_id=library['server_id'], section_id=library['section_id'],
|
||||
purge_only=purge_only))
|
||||
return all(success)
|
||||
|
||||
elif str(section_id).isdigit():
|
||||
server_id = server_id or plexpy.CONFIG.PMS_IDENTIFIER
|
||||
database.delete_library_history(server_id=server_id, section_id=section_id)
|
||||
if purge_only:
|
||||
return True
|
||||
else:
|
||||
return 'Unable to delete items, section_id not valid.'
|
||||
except Exception as e:
|
||||
logger.warn("Tautulli Libraries :: Unable to execute database query for delete_all_history: %s." % e)
|
||||
|
||||
def delete(self, section_id=None):
|
||||
monitor_db = database.MonitorDatabase()
|
||||
|
||||
logger.info("Tautulli Libraries :: Deleting library with server_id %s and section_id %s from database."
|
||||
% (server_id, section_id))
|
||||
try:
|
||||
if section_id.isdigit():
|
||||
self.delete_all_history(section_id)
|
||||
logger.info("Tautulli Libraries :: Deleting library with id %s from database." % section_id)
|
||||
monitor_db.action('UPDATE library_sections SET deleted_section = 1 WHERE section_id = ?', [section_id])
|
||||
monitor_db.action('UPDATE library_sections SET keep_history = 0 WHERE section_id = ?', [section_id])
|
||||
monitor_db.action('UPDATE library_sections SET do_notify = 0 WHERE section_id = ?', [section_id])
|
||||
monitor_db.action('UPDATE library_sections SET do_notify_created = 0 WHERE section_id = ?', [section_id])
|
||||
|
||||
library_cards = plexpy.CONFIG.HOME_LIBRARY_CARDS
|
||||
if section_id in library_cards:
|
||||
library_cards.remove(section_id)
|
||||
plexpy.CONFIG.__setattr__('HOME_LIBRARY_CARDS', library_cards)
|
||||
plexpy.CONFIG.write()
|
||||
|
||||
return 'Deleted library with id %s.' % section_id
|
||||
else:
|
||||
return 'Unable to delete library, section_id not valid.'
|
||||
monitor_db.action('UPDATE library_sections '
|
||||
'SET deleted_section = 1, keep_history = 0, do_notify = 0, do_notify_created = 0 '
|
||||
'WHERE server_id = ? AND section_id = ?', [server_id, section_id])
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warn("Tautulli Libraries :: Unable to execute database query for delete: %s." % e)
|
||||
|
||||
else:
|
||||
return False
|
||||
|
||||
def undelete(self, section_id=None, section_name=None):
|
||||
monitor_db = database.MonitorDatabase()
|
||||
|
||||
|
@ -1078,10 +1084,10 @@ class Libraries(object):
|
|||
result = monitor_db.select(query=query, args=[section_id])
|
||||
if result:
|
||||
logger.info("Tautulli Libraries :: Re-adding library with id %s to database." % section_id)
|
||||
monitor_db.action('UPDATE library_sections SET deleted_section = 0 WHERE section_id = ?', [section_id])
|
||||
monitor_db.action('UPDATE library_sections SET keep_history = 1 WHERE section_id = ?', [section_id])
|
||||
monitor_db.action('UPDATE library_sections SET do_notify = 1 WHERE section_id = ?', [section_id])
|
||||
monitor_db.action('UPDATE library_sections SET do_notify_created = 1 WHERE section_id = ?', [section_id])
|
||||
monitor_db.action('UPDATE library_sections '
|
||||
'SET deleted_section = 0, keep_history = 1, do_notify = 1, do_notify_created = 1 '
|
||||
'WHERE section_id = ?',
|
||||
[section_id])
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
@ -1091,10 +1097,10 @@ class Libraries(object):
|
|||
result = monitor_db.select(query=query, args=[section_name])
|
||||
if result:
|
||||
logger.info("Tautulli Libraries :: Re-adding library with name %s to database." % section_name)
|
||||
monitor_db.action('UPDATE library_sections SET deleted_section = 0 WHERE section_name = ?', [section_name])
|
||||
monitor_db.action('UPDATE library_sections SET keep_history = 1 WHERE section_name = ?', [section_name])
|
||||
monitor_db.action('UPDATE library_sections SET do_notify = 1 WHERE section_name = ?', [section_name])
|
||||
monitor_db.action('UPDATE library_sections SET do_notify_created = 1 WHERE section_name = ?', [section_name])
|
||||
monitor_db.action('UPDATE library_sections '
|
||||
'SET deleted_section = 0, keep_history = 1, do_notify = 1, do_notify_created = 1 '
|
||||
'WHERE section_name = ?',
|
||||
[section_name])
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
|
|
@ -339,6 +339,14 @@ def available_notification_actions():
|
|||
'body': 'An update is available for Tautulli (version {tautulli_update_version}).',
|
||||
'icon': 'fa-refresh',
|
||||
'media_types': ('server',)
|
||||
},
|
||||
{'label': 'Tautulli Database Corruption',
|
||||
'name': 'on_plexpydbcorrupt',
|
||||
'description': 'Trigger a notification if Tautulli database corruption is detected when backing up the database.',
|
||||
'subject': 'Tautulli ({server_name})',
|
||||
'body': 'Tautulli database corruption detected. Automatic cleanup of database backups is suspended.',
|
||||
'icon': 'fa-database',
|
||||
'media_types': ('server',)
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
@ -409,6 +409,7 @@ class PlexTV(object):
|
|||
"username": helpers.get_xml_attr(a, 'username'),
|
||||
"thumb": helpers.get_xml_attr(a, 'thumb'),
|
||||
"email": helpers.get_xml_attr(a, 'email'),
|
||||
"is_active": 1,
|
||||
"is_admin": 1,
|
||||
"is_home_user": helpers.get_xml_attr(a, 'home'),
|
||||
"is_allow_sync": 1,
|
||||
|
@ -436,6 +437,7 @@ class PlexTV(object):
|
|||
"username": helpers.get_xml_attr(a, 'title'),
|
||||
"thumb": helpers.get_xml_attr(a, 'thumb'),
|
||||
"email": helpers.get_xml_attr(a, 'email'),
|
||||
"is_active": 1,
|
||||
"is_admin": 0,
|
||||
"is_home_user": helpers.get_xml_attr(a, 'home'),
|
||||
"is_allow_sync": helpers.get_xml_attr(a, 'allowSync'),
|
||||
|
|
|
@ -2660,7 +2660,8 @@ class PmsConnect(object):
|
|||
'agent': library['agent'],
|
||||
'thumb': library['thumb'],
|
||||
'art': library['art'],
|
||||
'count': children_list['library_count']
|
||||
'count': children_list['library_count'],
|
||||
'is_active': 1
|
||||
}
|
||||
|
||||
if section_type == 'show':
|
||||
|
|
153
plexpy/users.py
153
plexpy/users.py
|
@ -51,7 +51,11 @@ def refresh_users():
|
|||
if result:
|
||||
monitor_db = database.MonitorDatabase()
|
||||
|
||||
# Keep track of user_id to update is_active status
|
||||
user_ids = [0] # Local user always considered active
|
||||
|
||||
for item in result:
|
||||
user_ids.append(helpers.cast_to_int(item['user_id']))
|
||||
|
||||
if item.get('shared_libraries'):
|
||||
item['shared_libraries'] = ';'.join(item['shared_libraries'])
|
||||
|
@ -75,6 +79,9 @@ def refresh_users():
|
|||
|
||||
monitor_db.upsert('users', item, keys_dict)
|
||||
|
||||
query = 'UPDATE users SET is_active = 0 WHERE user_id NOT IN ({})'.format(', '.join(['?'] * len(user_ids)))
|
||||
monitor_db.action(query=query, args=user_ids)
|
||||
|
||||
logger.info("Tautulli Users :: Users list refreshed.")
|
||||
return True
|
||||
else:
|
||||
|
@ -109,7 +116,8 @@ class Users(object):
|
|||
|
||||
group_by = 'session_history.reference_id' if grouping else 'session_history.id'
|
||||
|
||||
columns = ['users.user_id',
|
||||
columns = ['users.id AS row_id',
|
||||
'users.user_id',
|
||||
'(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" \
|
||||
THEN users.username ELSE users.friendly_name END) AS friendly_name',
|
||||
'users.thumb AS user_thumb',
|
||||
|
@ -119,7 +127,7 @@ class Users(object):
|
|||
ELSE 0 END) - SUM(CASE WHEN session_history.paused_counter IS NULL THEN 0 ELSE \
|
||||
session_history.paused_counter END) AS duration',
|
||||
'MAX(session_history.started) AS last_seen',
|
||||
'MAX(session_history.id) AS id',
|
||||
'MAX(session_history.id) AS history_row_id',
|
||||
'session_history_metadata.full_title AS last_played',
|
||||
'session_history.ip_address',
|
||||
'session_history.platform',
|
||||
|
@ -138,9 +146,10 @@ class Users(object):
|
|||
'session_history_metadata.originally_available_at',
|
||||
'session_history_metadata.guid',
|
||||
'session_history_media_info.transcode_decision',
|
||||
'users.do_notify as do_notify',
|
||||
'users.keep_history as keep_history',
|
||||
'users.allow_guest as allow_guest'
|
||||
'users.do_notify AS do_notify',
|
||||
'users.keep_history AS keep_history',
|
||||
'users.allow_guest AS allow_guest',
|
||||
'users.is_active AS is_active'
|
||||
]
|
||||
try:
|
||||
query = data_tables.ssp_query(table_name='users',
|
||||
|
@ -182,14 +191,15 @@ class Users(object):
|
|||
# Rename Mystery platform names
|
||||
platform = common.PLATFORM_NAME_OVERRIDES.get(item['platform'], item['platform'])
|
||||
|
||||
row = {'user_id': item['user_id'],
|
||||
row = {'row_id': item['row_id'],
|
||||
'user_id': item['user_id'],
|
||||
'friendly_name': item['friendly_name'],
|
||||
'user_thumb': user_thumb,
|
||||
'plays': item['plays'],
|
||||
'duration': item['duration'],
|
||||
'last_seen': item['last_seen'],
|
||||
'last_played': item['last_played'],
|
||||
'id': item['id'],
|
||||
'history_row_id': item['history_row_id'],
|
||||
'ip_address': item['ip_address'],
|
||||
'platform': platform,
|
||||
'player': item['player'],
|
||||
|
@ -206,7 +216,8 @@ class Users(object):
|
|||
'transcode_decision': item['transcode_decision'],
|
||||
'do_notify': helpers.checked(item['do_notify']),
|
||||
'keep_history': helpers.checked(item['keep_history']),
|
||||
'allow_guest': helpers.checked(item['allow_guest'])
|
||||
'allow_guest': helpers.checked(item['allow_guest']),
|
||||
'is_active': item['is_active']
|
||||
}
|
||||
|
||||
rows.append(row)
|
||||
|
@ -233,7 +244,7 @@ class Users(object):
|
|||
|
||||
custom_where = ['users.user_id', user_id]
|
||||
|
||||
columns = ['session_history.id',
|
||||
columns = ['session_history.id AS history_row_id',
|
||||
'MAX(session_history.started) AS last_seen',
|
||||
'session_history.ip_address',
|
||||
'COUNT(session_history.id) AS play_count',
|
||||
|
@ -293,7 +304,7 @@ class Users(object):
|
|||
# Rename Mystery platform names
|
||||
platform = common.PLATFORM_NAME_OVERRIDES.get(item["platform"], item["platform"])
|
||||
|
||||
row = {'id': item['id'],
|
||||
row = {'history_row_id': item['history_row_id'],
|
||||
'last_seen': item['last_seen'],
|
||||
'ip_address': item['ip_address'],
|
||||
'play_count': item['play_count'],
|
||||
|
@ -342,11 +353,13 @@ class Users(object):
|
|||
logger.warn("Tautulli Users :: Unable to execute database query for set_config: %s." % e)
|
||||
|
||||
def get_details(self, user_id=None, user=None, email=None):
|
||||
default_return = {'user_id': 0,
|
||||
default_return = {'row_id': 0,
|
||||
'user_id': 0,
|
||||
'username': 'Local',
|
||||
'friendly_name': 'Local',
|
||||
'user_thumb': common.DEFAULT_USER_THUMB,
|
||||
'email': '',
|
||||
'is_active': 1,
|
||||
'is_admin': '',
|
||||
'is_home_user': 0,
|
||||
'is_allow_sync': 0,
|
||||
|
@ -366,22 +379,28 @@ class Users(object):
|
|||
|
||||
try:
|
||||
if str(user_id).isdigit():
|
||||
query = 'SELECT user_id, username, friendly_name, thumb AS user_thumb, custom_avatar_url AS custom_thumb, ' \
|
||||
'email, is_admin, is_home_user, is_allow_sync, is_restricted, do_notify, keep_history, deleted_user, ' \
|
||||
query = 'SELECT id AS row_id, user_id, username, friendly_name, ' \
|
||||
'thumb AS user_thumb, custom_avatar_url AS custom_thumb, ' \
|
||||
'email, is_active, is_admin, is_home_user, is_allow_sync, is_restricted, ' \
|
||||
'do_notify, keep_history, deleted_user, ' \
|
||||
'allow_guest, shared_libraries ' \
|
||||
'FROM users ' \
|
||||
'WHERE user_id = ? '
|
||||
result = monitor_db.select(query, args=[user_id])
|
||||
elif user:
|
||||
query = 'SELECT user_id, username, friendly_name, thumb AS user_thumb, custom_avatar_url AS custom_thumb, ' \
|
||||
'email, is_admin, is_home_user, is_allow_sync, is_restricted, do_notify, keep_history, deleted_user, ' \
|
||||
query = 'SELECT id AS row_id, user_id, username, friendly_name, ' \
|
||||
'thumb AS user_thumb, custom_avatar_url AS custom_thumb, ' \
|
||||
'email, is_active, is_admin, is_home_user, is_allow_sync, is_restricted, ' \
|
||||
'do_notify, keep_history, deleted_user, ' \
|
||||
'allow_guest, shared_libraries ' \
|
||||
'FROM users ' \
|
||||
'WHERE username = ? COLLATE NOCASE '
|
||||
result = monitor_db.select(query, args=[user])
|
||||
elif email:
|
||||
query = 'SELECT user_id, username, friendly_name, thumb AS user_thumb, custom_avatar_url AS custom_thumb, ' \
|
||||
'email, is_admin, is_home_user, is_allow_sync, is_restricted, do_notify, keep_history, deleted_user, ' \
|
||||
query = 'SELECT id AS row_id, user_id, username, friendly_name, ' \
|
||||
'thumb AS user_thumb, custom_avatar_url AS custom_thumb, ' \
|
||||
'email, is_active, is_admin, is_home_user, is_allow_sync, is_restricted, ' \
|
||||
'do_notify, keep_history, deleted_user, ' \
|
||||
'allow_guest, shared_libraries ' \
|
||||
'FROM users ' \
|
||||
'WHERE email = ? COLLATE NOCASE '
|
||||
|
@ -411,11 +430,13 @@ class Users(object):
|
|||
|
||||
shared_libraries = tuple(item['shared_libraries'].split(';')) if item['shared_libraries'] else ()
|
||||
|
||||
user_details = {'user_id': item['user_id'],
|
||||
user_details = {'row_id': item['row_id'],
|
||||
'user_id': item['user_id'],
|
||||
'username': item['username'],
|
||||
'friendly_name': friendly_name,
|
||||
'user_thumb': user_thumb,
|
||||
'email': item['email'],
|
||||
'is_active': item['is_active'],
|
||||
'is_admin': item['is_admin'],
|
||||
'is_home_user': item['is_home_user'],
|
||||
'is_allow_sync': item['is_allow_sync'],
|
||||
|
@ -451,21 +472,25 @@ class Users(object):
|
|||
# Use "Local" user to retain compatibility with PlexWatch database value
|
||||
return default_return
|
||||
|
||||
def get_watch_time_stats(self, user_id=None, grouping=None):
|
||||
def get_watch_time_stats(self, user_id=None, grouping=None, query_days=None):
|
||||
if not session.allow_session_user(user_id):
|
||||
return []
|
||||
|
||||
if grouping is None:
|
||||
grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES
|
||||
|
||||
if query_days and query_days is not None:
|
||||
query_days = map(helpers.cast_to_int, query_days.split(','))
|
||||
else:
|
||||
query_days = [1, 7, 30, 0]
|
||||
|
||||
monitor_db = database.MonitorDatabase()
|
||||
|
||||
time_queries = [1, 7, 30, 0]
|
||||
user_watch_time_stats = []
|
||||
|
||||
group_by = 'reference_id' if grouping else 'id'
|
||||
|
||||
for days in time_queries:
|
||||
for days in query_days:
|
||||
try:
|
||||
if days > 0:
|
||||
if str(user_id).isdigit():
|
||||
|
@ -618,8 +643,8 @@ class Users(object):
|
|||
monitor_db = database.MonitorDatabase()
|
||||
|
||||
try:
|
||||
query = 'SELECT user_id, username, friendly_name, thumb, custom_avatar_url, email, ' \
|
||||
'is_admin, is_home_user, is_allow_sync, is_restricted, ' \
|
||||
query = 'SELECT id AS row_id, user_id, username, friendly_name, thumb, custom_avatar_url, email, ' \
|
||||
'is_active, is_admin, is_home_user, is_allow_sync, is_restricted, ' \
|
||||
'do_notify, keep_history, allow_guest, server_token, shared_libraries, ' \
|
||||
'filter_all, filter_movies, filter_tv, filter_music, filter_photos ' \
|
||||
'FROM users WHERE deleted_user = 0'
|
||||
|
@ -630,11 +655,13 @@ class Users(object):
|
|||
|
||||
users = []
|
||||
for item in result:
|
||||
user = {'user_id': item['user_id'],
|
||||
user = {'row_id': item['row_id'],
|
||||
'user_id': item['user_id'],
|
||||
'username': item['username'],
|
||||
'friendly_name': item['friendly_name'] or item['username'],
|
||||
'thumb': item['custom_avatar_url'] or item['thumb'],
|
||||
'email': item['email'],
|
||||
'is_active': item['is_active'],
|
||||
'is_admin': item['is_admin'],
|
||||
'is_home_user': item['is_home_user'],
|
||||
'is_allow_sync': item['is_allow_sync'],
|
||||
|
@ -654,54 +681,40 @@ class Users(object):
|
|||
|
||||
return users
|
||||
|
||||
def delete_all_history(self, user_id=None):
|
||||
def delete(self, user_id=None, row_ids=None, purge_only=False):
|
||||
monitor_db = database.MonitorDatabase()
|
||||
|
||||
try:
|
||||
if str(user_id).isdigit():
|
||||
logger.info("Tautulli Users :: Deleting all history for user id %s from database." % user_id)
|
||||
session_history_media_info_del = \
|
||||
monitor_db.action('DELETE FROM '
|
||||
'session_history_media_info '
|
||||
'WHERE session_history_media_info.id IN (SELECT session_history_media_info.id '
|
||||
'FROM session_history_media_info '
|
||||
'JOIN session_history ON session_history_media_info.id = session_history.id '
|
||||
'WHERE session_history.user_id = ?)', [user_id])
|
||||
session_history_metadata_del = \
|
||||
monitor_db.action('DELETE FROM '
|
||||
'session_history_metadata '
|
||||
'WHERE session_history_metadata.id IN (SELECT session_history_metadata.id '
|
||||
'FROM session_history_metadata '
|
||||
'JOIN session_history ON session_history_metadata.id = session_history.id '
|
||||
'WHERE session_history.user_id = ?)', [user_id])
|
||||
session_history_del = \
|
||||
monitor_db.action('DELETE FROM '
|
||||
'session_history '
|
||||
'WHERE session_history.user_id = ?', [user_id])
|
||||
if row_ids and row_ids is not None:
|
||||
row_ids = map(helpers.cast_to_int, row_ids.split(','))
|
||||
|
||||
return 'Deleted all items for user_id %s.' % user_id
|
||||
# Get the user_ids corresponding to the row_ids
|
||||
result = monitor_db.select('SELECT user_id FROM users '
|
||||
'WHERE id IN ({})'.format(','.join(['?'] * len(row_ids))), row_ids)
|
||||
|
||||
success = []
|
||||
for user in result:
|
||||
success.append(self.delete(user_id=user['user_id'],
|
||||
purge_only=purge_only))
|
||||
return all(success)
|
||||
|
||||
elif str(user_id).isdigit():
|
||||
database.delete_user_history(user_id=user_id)
|
||||
if purge_only:
|
||||
return True
|
||||
else:
|
||||
return 'Unable to delete items. Input user_id not valid.'
|
||||
except Exception as e:
|
||||
logger.warn("Tautulli Users :: Unable to execute database query for delete_all_history: %s." % e)
|
||||
|
||||
def delete(self, user_id=None):
|
||||
monitor_db = database.MonitorDatabase()
|
||||
|
||||
logger.info("Tautulli Users :: Deleting user with user_id %s from database."
|
||||
% user_id)
|
||||
try:
|
||||
if str(user_id).isdigit():
|
||||
self.delete_all_history(user_id)
|
||||
logger.info("Tautulli Users :: Deleting user with id %s from database." % user_id)
|
||||
monitor_db.action('UPDATE users SET deleted_user = 1 WHERE user_id = ?', [user_id])
|
||||
monitor_db.action('UPDATE users SET keep_history = 0 WHERE user_id = ?', [user_id])
|
||||
monitor_db.action('UPDATE users SET do_notify = 0 WHERE user_id = ?', [user_id])
|
||||
|
||||
return 'Deleted user with id %s.' % user_id
|
||||
else:
|
||||
return 'Unable to delete user, user_id not valid.'
|
||||
monitor_db.action('UPDATE users '
|
||||
'SET deleted_user = 1, keep_history = 0, do_notify = 0 '
|
||||
'WHERE user_id = ?', [user_id])
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warn("Tautulli Users :: Unable to execute database query for delete: %s." % e)
|
||||
|
||||
else:
|
||||
return False
|
||||
|
||||
def undelete(self, user_id=None, username=None):
|
||||
monitor_db = database.MonitorDatabase()
|
||||
|
||||
|
@ -711,9 +724,9 @@ class Users(object):
|
|||
result = monitor_db.select(query=query, args=[user_id])
|
||||
if result:
|
||||
logger.info("Tautulli Users :: Re-adding user with id %s to database." % user_id)
|
||||
monitor_db.action('UPDATE users SET deleted_user = 0 WHERE user_id = ?', [user_id])
|
||||
monitor_db.action('UPDATE users SET keep_history = 1 WHERE user_id = ?', [user_id])
|
||||
monitor_db.action('UPDATE users SET do_notify = 1 WHERE user_id = ?', [user_id])
|
||||
monitor_db.action('UPDATE users '
|
||||
'SET deleted_user = 0, keep_history = 1, do_notify = 1 '
|
||||
'WHERE user_id = ?', [user_id])
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
@ -723,9 +736,9 @@ class Users(object):
|
|||
result = monitor_db.select(query=query, args=[username])
|
||||
if result:
|
||||
logger.info("Tautulli Users :: Re-adding user with username %s to database." % username)
|
||||
monitor_db.action('UPDATE users SET deleted_user = 0 WHERE username = ?', [username])
|
||||
monitor_db.action('UPDATE users SET keep_history = 1 WHERE username = ?', [username])
|
||||
monitor_db.action('UPDATE users SET do_notify = 1 WHERE username = ?', [username])
|
||||
monitor_db.action('UPDATE users '
|
||||
'SET deleted_user = 0, keep_history = 1, do_notify = 1 '
|
||||
'WHERE username = ?', [username])
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
|
|
@ -484,7 +484,8 @@ class WebInterface(object):
|
|||
"do_notify_created": "Checked",
|
||||
"duration": 1578037,
|
||||
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
|
||||
"id": 1128,
|
||||
"histroy_row_id": 1128,
|
||||
"is_active": 1,
|
||||
"keep_history": "Checked",
|
||||
"labels": [],
|
||||
"last_accessed": 1462693216,
|
||||
|
@ -500,9 +501,11 @@ class WebInterface(object):
|
|||
"parent_title": "",
|
||||
"plays": 772,
|
||||
"rating_key": 153037,
|
||||
"row_id": 1,
|
||||
"section_id": 2,
|
||||
"section_name": "TV Shows",
|
||||
"section_type": "Show",
|
||||
"server_id": "ds48g4r354a8v9byrrtr697g3g79w",
|
||||
"thumb": "/library/metadata/153036/thumb/1462175062",
|
||||
"year": 2016
|
||||
},
|
||||
|
@ -875,13 +878,16 @@ class WebInterface(object):
|
|||
"deleted_section": 0,
|
||||
"do_notify": 1,
|
||||
"do_notify_created": 1,
|
||||
"is_active": 1,
|
||||
"keep_history": 1,
|
||||
"library_art": "/:/resources/movie-fanart.jpg",
|
||||
"library_thumb": "/:/resources/movie.png",
|
||||
"parent_count": null,
|
||||
"row_id": 1,
|
||||
"section_id": 1,
|
||||
"section_name": "Movies",
|
||||
"section_type": "movie"
|
||||
"section_type": "movie",
|
||||
"server_id": "ds48g4r354a8v9byrrtr697g3g79w"
|
||||
}
|
||||
```
|
||||
"""
|
||||
|
@ -899,7 +905,7 @@ class WebInterface(object):
|
|||
@cherrypy.tools.json_out()
|
||||
@requireAuth(member_of("admin"))
|
||||
@addtoapi()
|
||||
def get_library_watch_time_stats(self, section_id=None, grouping=None, **kwargs):
|
||||
def get_library_watch_time_stats(self, section_id=None, grouping=None, query_days=None, **kwargs):
|
||||
""" Get a library's watch time statistics.
|
||||
|
||||
```
|
||||
|
@ -908,6 +914,7 @@ class WebInterface(object):
|
|||
|
||||
Optional parameters:
|
||||
grouping (int): 0 or 1
|
||||
query_days (str): Comma separated days, e.g. "1,7,30,0"
|
||||
|
||||
Returns:
|
||||
json:
|
||||
|
@ -934,7 +941,8 @@ class WebInterface(object):
|
|||
|
||||
if section_id:
|
||||
library_data = libraries.Libraries()
|
||||
result = library_data.get_watch_time_stats(section_id=section_id, grouping=grouping)
|
||||
result = library_data.get_watch_time_stats(section_id=section_id, grouping=grouping,
|
||||
query_days=query_days)
|
||||
if result:
|
||||
return result
|
||||
else:
|
||||
|
@ -989,7 +997,7 @@ class WebInterface(object):
|
|||
@cherrypy.tools.json_out()
|
||||
@requireAuth(member_of("admin"))
|
||||
@addtoapi()
|
||||
def delete_all_library_history(self, section_id, **kwargs):
|
||||
def delete_all_library_history(self, server_id=None, section_id=None, row_ids=None, **kwargs):
|
||||
""" Delete all Tautulli history for a specific library.
|
||||
|
||||
```
|
||||
|
@ -997,27 +1005,28 @@ class WebInterface(object):
|
|||
section_id (str): The id of the Plex library section
|
||||
|
||||
Optional parameters:
|
||||
None
|
||||
server_id (str): The Plex server identifier of the library section
|
||||
row_ids (str): Comma separated row ids to delete, e.g. "2,3,8"
|
||||
|
||||
Returns:
|
||||
None
|
||||
```
|
||||
"""
|
||||
if (server_id and section_id) or row_ids:
|
||||
library_data = libraries.Libraries()
|
||||
|
||||
if section_id:
|
||||
delete_row = library_data.delete_all_history(section_id=section_id)
|
||||
|
||||
if delete_row:
|
||||
return {'message': delete_row}
|
||||
success = library_data.delete(server_id=server_id, section_id=section_id, row_ids=row_ids, purge_only=True)
|
||||
if success:
|
||||
return {'result': 'success', 'message': 'Deleted library history.'}
|
||||
else:
|
||||
return {'message': 'no data received'}
|
||||
return {'result': 'error', 'message': 'Failed to delete library(s) history.'}
|
||||
else:
|
||||
return {'result': 'error', 'message': 'No server id and section id or row ids received.'}
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out()
|
||||
@requireAuth(member_of("admin"))
|
||||
@addtoapi()
|
||||
def delete_library(self, section_id, **kwargs):
|
||||
def delete_library(self, server_id=None, section_id=None, row_ids=None, **kwargs):
|
||||
""" Delete a library section from Tautulli. Also erases all history for the library.
|
||||
|
||||
```
|
||||
|
@ -1025,21 +1034,22 @@ class WebInterface(object):
|
|||
section_id (str): The id of the Plex library section
|
||||
|
||||
Optional parameters:
|
||||
None
|
||||
server_id (str): The Plex server identifier of the library section
|
||||
row_ids (str): Comma separated row ids to delete, e.g. "2,3,8"
|
||||
|
||||
Returns:
|
||||
None
|
||||
```
|
||||
"""
|
||||
if (server_id and section_id) or row_ids:
|
||||
library_data = libraries.Libraries()
|
||||
|
||||
if section_id:
|
||||
delete_row = library_data.delete(section_id=section_id)
|
||||
|
||||
if delete_row:
|
||||
return {'message': delete_row}
|
||||
success = library_data.delete(server_id=server_id, section_id=section_id, row_ids=row_ids)
|
||||
if success:
|
||||
return {'result': 'success', 'message': 'Deleted library.'}
|
||||
else:
|
||||
return {'message': 'no data received'}
|
||||
return {'result': 'error', 'message': 'Failed to delete library(s).'}
|
||||
else:
|
||||
return {'result': 'error', 'message': 'No server id and section id or row ids received.'}
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out()
|
||||
|
@ -1155,8 +1165,9 @@ class WebInterface(object):
|
|||
"duration": 2998290,
|
||||
"friendly_name": "Jon Snow",
|
||||
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
|
||||
"id": 1121,
|
||||
"history_row_id": 1121,
|
||||
"ip_address": "xxx.xxx.xxx.xxx",
|
||||
"is_active": 1,
|
||||
"keep_history": "Checked",
|
||||
"last_played": "Game of Thrones - The Red Woman",
|
||||
"last_seen": 1462591869,
|
||||
|
@ -1170,6 +1181,7 @@ class WebInterface(object):
|
|||
"player": "Plex Web (Chrome)",
|
||||
"plays": 487,
|
||||
"rating_key": 153037,
|
||||
"row_id": 1,
|
||||
"thumb": "/library/metadata/153036/thumb/1462175062",
|
||||
"transcode_decision": "transcode",
|
||||
"user_id": 133788,
|
||||
|
@ -1493,10 +1505,13 @@ class WebInterface(object):
|
|||
"do_notify": 1,
|
||||
"email": "Jon.Snow.1337@CastleBlack.com",
|
||||
"friendly_name": "Jon Snow",
|
||||
"is_active": 1,
|
||||
"is_admin": 0,
|
||||
"is_allow_sync": 1,
|
||||
"is_home_user": 1,
|
||||
"is_restricted": 0,
|
||||
"keep_history": 1,
|
||||
"row_id": 1,
|
||||
"shared_libraries": ["10", "1", "4", "5", "15", "20", "2"],
|
||||
"user_id": 133788,
|
||||
"user_thumb": "https://plex.tv/users/k10w42309cynaopq/avatar",
|
||||
|
@ -1518,7 +1533,7 @@ class WebInterface(object):
|
|||
@cherrypy.tools.json_out()
|
||||
@requireAuth(member_of("admin"))
|
||||
@addtoapi()
|
||||
def get_user_watch_time_stats(self, user_id=None, grouping=None, **kwargs):
|
||||
def get_user_watch_time_stats(self, user_id=None, grouping=None, query_days=None, **kwargs):
|
||||
""" Get a user's watch time statistics.
|
||||
|
||||
```
|
||||
|
@ -1527,6 +1542,7 @@ class WebInterface(object):
|
|||
|
||||
Optional parameters:
|
||||
grouping (int): 0 or 1
|
||||
query_days (str): Comma separated days, e.g. "1,7,30,0"
|
||||
|
||||
Returns:
|
||||
json:
|
||||
|
@ -1553,7 +1569,7 @@ class WebInterface(object):
|
|||
|
||||
if user_id:
|
||||
user_data = users.Users()
|
||||
result = user_data.get_watch_time_stats(user_id=user_id, grouping=grouping)
|
||||
result = user_data.get_watch_time_stats(user_id=user_id, grouping=grouping, query_days=query_days)
|
||||
if result:
|
||||
return result
|
||||
else:
|
||||
|
@ -1608,7 +1624,7 @@ class WebInterface(object):
|
|||
@cherrypy.tools.json_out()
|
||||
@requireAuth(member_of("admin"))
|
||||
@addtoapi()
|
||||
def delete_all_user_history(self, user_id, **kwargs):
|
||||
def delete_all_user_history(self, user_id=None, row_ids=None, **kwargs):
|
||||
""" Delete all Tautulli history for a specific user.
|
||||
|
||||
```
|
||||
|
@ -1616,25 +1632,27 @@ class WebInterface(object):
|
|||
user_id (str): The id of the Plex user
|
||||
|
||||
Optional parameters:
|
||||
None
|
||||
row_ids (str): Comma separated row ids to delete, e.g. "2,3,8"
|
||||
|
||||
Returns:
|
||||
None
|
||||
```
|
||||
"""
|
||||
if user_id:
|
||||
if user_id or row_ids:
|
||||
user_data = users.Users()
|
||||
delete_row = user_data.delete_all_history(user_id=user_id)
|
||||
if delete_row:
|
||||
return {'message': delete_row}
|
||||
success = user_data.delete(user_id=user_id, row_ids=row_ids, purge_only=True)
|
||||
if success:
|
||||
return {'result': 'success', 'message': 'Deleted user history.'}
|
||||
else:
|
||||
return {'message': 'no data received'}
|
||||
return {'result': 'error', 'message': 'Failed to delete user(s) history.'}
|
||||
else:
|
||||
return {'result': 'error', 'message': 'No user id or row ids received.'}
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out()
|
||||
@requireAuth(member_of("admin"))
|
||||
@addtoapi()
|
||||
def delete_user(self, user_id, **kwargs):
|
||||
def delete_user(self, user_id=None, row_ids=None, **kwargs):
|
||||
""" Delete a user from Tautulli. Also erases all history for the user.
|
||||
|
||||
```
|
||||
|
@ -1642,19 +1660,21 @@ class WebInterface(object):
|
|||
user_id (str): The id of the Plex user
|
||||
|
||||
Optional parameters:
|
||||
None
|
||||
row_ids (str): Comma separated row ids to delete, e.g. "2,3,8"
|
||||
|
||||
Returns:
|
||||
None
|
||||
```
|
||||
"""
|
||||
if user_id:
|
||||
if user_id or row_ids:
|
||||
user_data = users.Users()
|
||||
delete_row = user_data.delete(user_id=user_id)
|
||||
if delete_row:
|
||||
return {'message': delete_row}
|
||||
success = user_data.delete(user_id=user_id, row_ids=row_ids)
|
||||
if success:
|
||||
return {'result': 'success', 'message': 'Deleted user.'}
|
||||
else:
|
||||
return {'message': 'no data received'}
|
||||
return {'result': 'error', 'message': 'Failed to delete user(s).'}
|
||||
else:
|
||||
return {'result': 'error', 'message': 'No user id or row ids received.'}
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out()
|
||||
|
@ -1742,7 +1762,6 @@ class WebInterface(object):
|
|||
"group_count": 1,
|
||||
"group_ids": "1124",
|
||||
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
|
||||
"id": 1124,
|
||||
"ip_address": "xxx.xxx.xxx.xxx",
|
||||
"live": 0,
|
||||
"media_index": 17,
|
||||
|
@ -1758,6 +1777,7 @@ class WebInterface(object):
|
|||
"player": "Castle-PC",
|
||||
"rating_key": 4348,
|
||||
"reference_id": 1123,
|
||||
"row_id": 1124,
|
||||
"session_key": null,
|
||||
"started": 1462688107,
|
||||
"state": null,
|
||||
|
@ -1932,16 +1952,32 @@ class WebInterface(object):
|
|||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out()
|
||||
@requireAuth(member_of("admin"))
|
||||
def delete_history_rows(self, row_id, **kwargs):
|
||||
@addtoapi("delete_history")
|
||||
def delete_history_rows(self, row_ids=None, **kwargs):
|
||||
""" Delete history rows from Tautulli.
|
||||
|
||||
```
|
||||
Required parameters:
|
||||
row_ids (str): Comma separated row ids to delete, e.g. "65,110,2,3645"
|
||||
|
||||
Optional parameters:
|
||||
None
|
||||
|
||||
Returns:
|
||||
None
|
||||
```
|
||||
"""
|
||||
data_factory = datafactory.DataFactory()
|
||||
|
||||
if row_id:
|
||||
delete_row = data_factory.delete_session_history_rows(row_id=row_id)
|
||||
if row_ids:
|
||||
success = database.delete_session_history_rows(row_ids=row_ids)
|
||||
|
||||
if delete_row:
|
||||
return {'message': delete_row}
|
||||
if success:
|
||||
return {'result': 'success', 'message': 'Deleted history.'}
|
||||
else:
|
||||
return {'message': 'no data received'}
|
||||
return {'result': 'error', 'message': 'Failed to delete history.'}
|
||||
else:
|
||||
return {'result': 'error', 'message': 'No row ids received.'}
|
||||
|
||||
|
||||
##### Graphs #####
|
||||
|
@ -4296,7 +4332,7 @@ class WebInterface(object):
|
|||
raise Exception('PMS image request failed')
|
||||
|
||||
except Exception as e:
|
||||
logger.warn(u'Failed to get image %s, falling back to %s.' % (img, fallback))
|
||||
logger.warn("Failed to get image %s, falling back to %s." % (img, fallback))
|
||||
if fallback in common.DEFAULT_IMAGES:
|
||||
fbi = common.DEFAULT_IMAGES[fallback]
|
||||
fp = os.path.join(plexpy.PROG_DIR, 'data', fbi)
|
||||
|
@ -5440,6 +5476,7 @@ class WebInterface(object):
|
|||
[{"art": "/:/resources/show-fanart.jpg",
|
||||
"child_count": "3745",
|
||||
"count": "62",
|
||||
"is_active": 1,
|
||||
"parent_count": "240",
|
||||
"section_id": "2",
|
||||
"section_name": "TV Shows",
|
||||
|
@ -5483,11 +5520,13 @@ class WebInterface(object):
|
|||
"filter_music": "",
|
||||
"filter_photos": "",
|
||||
"filter_tv": "",
|
||||
"is_active": 1,
|
||||
"is_admin": 0,
|
||||
"is_allow_sync": 1,
|
||||
"is_home_user": 1,
|
||||
"is_restricted": 0,
|
||||
"keep_history": 1,
|
||||
"row_id": 1,
|
||||
"server_token": "PU9cMuQZxJKFBtGqHk68",
|
||||
"shared_libraries": "1;2;3",
|
||||
"thumb": "https://plex.tv/users/k10w42309cynaopq/avatar",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue