diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css
index 0b6934e3..fedbf8d4 100644
--- a/data/interfaces/default/css/plexpy.css
+++ b/data/interfaces/default/css/plexpy.css
@@ -2416,7 +2416,41 @@ a .home-platforms-instance-list-oval:hover,
width: 100%;
}
}
-
+table.display tr.shown + tr div.slider {
+ display: none;
+}
+table.display tr.shown + tr > td {
+ padding-top: 0;
+ padding-bottom: 0;
+}
+table.display tr.shown + tr:hover {
+ background-color: rgba(255,255,255,0);
+}
+table.display tr.shown + tr:hover a,
+table.display tr.shown + tr td:hover a,
+table.display tr.shown + tr .pagination > .active > a,
+table.display tr.shown + tr .pagination > .active > a:hover {
+ color: #fff;
+}
+table.display tr.shown + tr table[id^='history_child'] td:hover a {
+ color: #F9AA03;
+}
+table.display tr.shown + tr .pagination > .disabled > a {
+ color: #444444;
+}
+table.display tr.shown + tr .pagination > li > a:hover {
+ color: #23527c;
+}
+table[id^='history_child'] {
+ margin-top: 0;
+ margin-left: -4px;
+ opacity: .6;
+}
+table[id^='history_child'] thead th {
+ line-height: 0;
+ height: 0 !important;
+ overflow: hidden;
+}
#search_form {
width: 350px;
padding: 8px 15px;
diff --git a/data/interfaces/default/js/tables/history_table.js b/data/interfaces/default/js/tables/history_table.js
index ae303bf6..ec086ba2 100644
--- a/data/interfaces/default/js/tables/history_table.js
+++ b/data/interfaces/default/js/tables/history_table.js
@@ -46,13 +46,18 @@ history_table_options = {
"createdCell": function (td, cellData, rowData, row, col) {
if (rowData['stopped'] === null) {
$(td).html('Currently watching...');
+ } else if (rowData['group_count'] > 1) {
+ date = moment(cellData, "X").format(date_format);
+ expand_history = '';
+ $(td).html('
');
} else {
- $(td).html(moment(cellData,"X").format(date_format));
+ date = moment(cellData, "X").format(date_format);
+ $(td).html(' ' + date + '
');
}
},
"searchable": false,
"width": "8%",
- "className": "no-wrap"
+ "className": "no-wrap expand-history"
},
{
"targets": [2],
@@ -83,7 +88,8 @@ history_table_options = {
$(td).html('n/a');
}
} else {
- $(td).html(' ' + cellData + '');
+ external_ip = '';
+ $(td).html(''+ external_ip + cellData + '');
}
} else {
$(td).html('n/a');
@@ -104,8 +110,8 @@ history_table_options = {
transcode_dec = '';
} else if (rowData['video_decision'] === 'direct play' || rowData['audio_decision'] === 'direct play') {
transcode_dec = '';
- }
- $(td).html('');
+ }
+ $(td).html('');
}
},
"width": "15%",
@@ -121,16 +127,16 @@ history_table_options = {
if (rowData['media_type'] === 'movie') {
media_type = '';
thumb_popover = '' + cellData + ' (' + rowData['year'] + ')'
- $(td).html('');
+ $(td).html('');
} else if (rowData['media_type'] === 'episode') {
media_type = '';
thumb_popover = '' + cellData + ' \
(S' + rowData['parent_media_index'] + '· E' + rowData['media_index'] + ')'
- $(td).html('');
+ $(td).html('');
} else if (rowData['media_type'] === 'track') {
media_type = '';
thumb_popover = '' + cellData + ' (' + rowData['parent_title'] + ')'
- $(td).html('');
+ $(td).html('');
} else {
$(td).html('' + cellData + '');
}
@@ -155,7 +161,7 @@ history_table_options = {
{
"targets": [7],
"data":"paused_counter",
- "render": function ( data, type, full ) {
+ "render": function (data, type, full) {
if (data !== null) {
return Math.round(moment.duration(data, 'seconds').as('minutes')) + ' mins';
} else {
@@ -183,7 +189,7 @@ history_table_options = {
{
"targets": [9],
"data":"duration",
- "render": function ( data, type, full ) {
+ "render": function (data, type, full) {
if (data !== null) {
return Math.round(moment.duration(data, 'seconds').as('minutes')) + ' mins';
} else {
@@ -196,11 +202,11 @@ history_table_options = {
},
{
"targets": [10],
- "data":"percent_complete",
- "render": function ( data, type, full ) {
- if (data > 80) {
+ "data": "watched_status",
+ "render": function (data, type, full) {
+ if (data == 1) {
return ''
- } else if (data > 40) {
+ } else if (data == 0.5) {
return ''
} else {
return ''
@@ -218,6 +224,8 @@ history_table_options = {
$('#ajaxMsg').fadeOut();
// Create the tooltips.
+ $('.expand-history-tooltip').tooltip({ container: 'body' });
+ $('.external-ip-tooltip').tooltip();
$('.transcode-tooltip').tooltip();
$('.media-type-tooltip').tooltip();
$('.watched-tooltip').tooltip();
@@ -231,24 +239,57 @@ history_table_options = {
});
if ($('#row-edit-mode').hasClass('active')) {
- $('.delete-control').each(function() {
+ $('.delete-control').each(function () {
$(this).removeClass('hidden');
});
}
+
+ history_table.rows().every(function () {
+ var rowData = this.data();
+ if (rowData['group_count'] != 1 && rowData['reference_id'] in history_child_table) {
+ // if grouped row and a child table was already created
+ $(this.node()).find('i.fa').toggleClass('fa-plus-circle').toggleClass('fa-minus-circle');
+ this.child(childTableFormat(rowData)).show();
+ createChildTable(this, rowData)
+ }
+ });
},
"preDrawCallback": function(settings) {
var msg = " Fetching rows...";
showMsg(msg, false, false, 0)
},
- "rowCallback": function (row, rowData) {
- if ($.inArray(rowData['id'], history_to_delete) !== -1) {
+ "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');
+ }
+ } else {
+ // if grouped rows
+ // toggle the parent button to danger
$(row).find('button[data-id="' + rowData['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');
+ }
+ });
}
+
+ if (rowData['group_count'] != 1 && rowData['reference_id'] in history_child_table) {
+ // if grouped row and a child table was already created
+ $(row).addClass('shown')
+ history_table.row(row).child(childTableFormat(rowData)).show();
+ }
+
}
}
-$('#history_table').on('click', 'td.modal-control', function () {
- var tr = $(this).parents('tr');
+// Parent table platform modal
+$('#history_table').on('click', '> tbody > tr > td.modal-control', function () {
+ var tr = $(this).closest('tr');
var row = history_table.row( tr );
var rowData = row.data();
@@ -266,8 +307,9 @@ $('#history_table').on('click', 'td.modal-control', function () {
showStreamDetails();
});
-$('#history_table').on('click', 'td.modal-control-ip', function () {
- var tr = $(this).parents('tr');
+// Parent table ip address modal
+$('#history_table').on('click', '> tbody > tr > td.modal-control-ip', function () {
+ var tr = $(this).closest('tr');
var row = history_table.row( tr );
var rowData = row.data();
@@ -288,16 +330,238 @@ $('#history_table').on('click', 'td.modal-control-ip', function () {
getUserLocation(rowData['ip_address']);
});
-$('#history_table').on('click', 'td.delete-control > button', function () {
- var tr = $(this).parents('tr');
+// Parent table delete mode
+$('#history_table').on('click', '> tbody > tr > td.delete-control > button', function () {
+ var tr = $(this).closest('tr');
var row = history_table.row( tr );
var rowData = row.data();
- var index = $.inArray(rowData['id'], history_to_delete);
- if (index === -1) {
- history_to_delete.push(rowData['id']);
+ 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);
+ if (index === -1) {
+ history_to_delete.push(rowData['id']);
+ } else {
+ history_to_delete.splice(index, 1);
+ }
+ $(this).toggleClass('btn-warning').toggleClass('btn-danger');
} else {
- history_to_delete.splice(index, 1);
+ // if grouped rows
+ if ($(this).hasClass('btn-warning')) {
+ // add all grouped rows to history_to_delete
+ var group_ids = rowData['group_ids'].split(',').map(Number);
+ group_ids.forEach(function (id) {
+ var index = $.inArray(id, history_to_delete);
+ if (index == -1) {
+ history_to_delete.push(id);
+ }
+ });
+ $(this).toggleClass('btn-warning').toggleClass('btn-danger');
+ if (row.child.isShown()) {
+ // if child table is visible, toggle all child buttons to danger
+ tr.next().find('td.delete-control > button.btn-warning').toggleClass('btn-warning').toggleClass('btn-danger');
+ }
+ } else {
+ // remove all grouped rows to history_to_delete
+ var group_ids = rowData['group_ids'].split(',').map(Number);
+ group_ids.forEach(function (id) {
+ var index = $.inArray(id, history_to_delete);
+ if (index != -1) {
+ history_to_delete.splice(index, 1);
+ }
+ });
+ $(this).toggleClass('btn-warning').toggleClass('btn-danger');
+ if (row.child.isShown()) {
+ // if child table is visible, toggle all child buttons to warning
+ tr.next().find('td.delete-control > button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger');
+ }
+ }
}
- $(this).toggleClass('btn-warning').toggleClass('btn-danger');
-});
\ No newline at end of file
+});
+
+// Parent table expand detailed history
+$('#history_table').on('click', '> tbody > tr > td.expand-history a', function () {
+ var tr = $(this).closest('tr');
+ var row = history_table.row(tr);
+ var rowData = row.data();
+
+ $(this).find('i.fa').toggleClass('fa-plus-circle').toggleClass('fa-minus-circle');
+
+ if (row.child.isShown()) {
+ $('div.slider', row.child()).slideUp(function () {
+ row.child.hide();
+ tr.removeClass('shown');
+ delete history_child_table[rowData['reference_id']];
+ });
+ } else {
+ tr.addClass('shown');
+ row.child(childTableFormat(rowData)).show();
+ createChildTable(row, rowData);
+ }
+});
+
+
+// Initialize the detailed history child table options using the parent table options
+function childTableOptions(rowData) {
+ history_child_options = history_table_options;
+ // Remove settings that are not necessary
+ history_child_options.searching = false;
+ history_child_options.lengthChange = false;
+ history_child_options.info = false;
+ history_child_options.pageLength = 10;
+ history_child_options.bStateSave = false;
+ history_child_options.ajax = {
+ "url": "get_history",
+ type: "post",
+ data: function (d) {
+ return {
+ 'json_data': JSON.stringify(d),
+ 'grouping': false,
+ 'reference_id': rowData['reference_id']
+ };
+ }
+ }
+ history_child_options.fnDrawCallback = function (settings) {
+ $('#ajaxMsg').fadeOut();
+
+ // Create the tooltips.
+ $('.expand-history-tooltip').tooltip({ container: 'body' });
+ $('.external-ip-tooltip').tooltip();
+ $('.transcode-tooltip').tooltip();
+ $('.media-type-tooltip').tooltip();
+ $('.watched-tooltip').tooltip();
+ $('.thumb-tooltip').popover({
+ html: true,
+ trigger: 'hover',
+ placement: 'right',
+ content: function () {
+ return '';
+ }
+ });
+
+ if ($('#row-edit-mode').hasClass('active')) {
+ $('.delete-control').each(function () {
+ $(this).removeClass('hidden');
+ });
+ }
+
+ $(this).closest('div.slider').slideDown();
+ }
+
+ return history_child_options;
+}
+
+// Format the detailed history child table
+function childTableFormat(rowData) {
+ return '' +
+ '
' +
+ '' +
+ '' +
+ 'Delete | ' +
+ 'Time | ' +
+ 'User | ' +
+ 'IP Address | ' +
+ 'Platform | ' +
+ 'Title | ' +
+ 'Started | ' +
+ 'Paused | ' +
+ 'Stopped | ' +
+ 'Duration | ' +
+ ' | ' +
+ '
' +
+ '' +
+ '' +
+ '' +
+ '
' +
+ '
';
+}
+
+// Create the detailed history child table
+history_child_table = {};
+function createChildTable(row, rowData) {
+ history_child_options = childTableOptions(rowData);
+ // initialize the child table
+ history_child_table[rowData['reference_id']] = $('#history_child-' + rowData['reference_id']).DataTable(history_child_options);
+
+ // Set child table column visibility to match parent table
+ var visibility = history_table.columns().visible();
+ for (var i = 0; i < visibility.length; i++) {
+ if (!(visibility[i])) { history_child_table[rowData['reference_id']].column(i).visible(visibility[i]); }
+ }
+ history_table.on('column-visibility', function (e, settings, colIdx, visibility) {
+ if (row.child.isShown()) {
+ history_child_table[rowData['reference_id']].column(colIdx).visible(visibility);
+ }
+ });
+
+ // Child table platform modal
+ $('#history_child-' + rowData['reference_id']).on('click', 'td.modal-control', function () {
+ var tr = $(this).closest('tr');
+ var childRow = history_child_table[rowData['reference_id']].row(tr);
+ var childRowData = childRow.data();
+
+ function showStreamDetails() {
+ $.ajax({
+ url: 'get_stream_data',
+ data: { row_id: childRowData['id'], user: childRowData['friendly_name'] },
+ cache: false,
+ async: true,
+ complete: function (xhr, status) {
+ $("#info-modal").html(xhr.responseText);
+ }
+ });
+ }
+ showStreamDetails();
+ });
+
+ // Child table ip address modal
+ $('#history_child-' + rowData['reference_id']).on('click', 'td.modal-control-ip', function () {
+ var tr = $(this).closest('tr');
+ var childRow = history_child_table[rowData['reference_id']].row(tr);
+ var childRowData = childRow.data();
+
+ function getUserLocation(ip_address) {
+ if (isPrivateIP(ip_address)) {
+ return "n/a"
+ } else {
+ $.ajax({
+ url: 'get_ip_address_details',
+ data: { ip_address: ip_address },
+ async: true,
+ complete: function (xhr, status) {
+ $("#ip-info-modal").html(xhr.responseText);
+ }
+ });
+ }
+ }
+ getUserLocation(childRowData['ip_address']);
+ });
+
+ // Child table delete mode
+ $('#history_child-' + rowData['reference_id']).on('click', 'td.delete-control > button', function () {
+ var tr = $(this).closest('tr');
+ var childRow = history_child_table[rowData['reference_id']].row(tr);
+ var childRowData = childRow.data();
+
+ // add or remove row from history_to_delete
+ var index = $.inArray(childRowData['id'], history_to_delete);
+ if (index === -1) {
+ history_to_delete.push(childRowData['id']);
+ } else {
+ history_to_delete.splice(index, 1);
+ }
+ $(this).toggleClass('btn-warning').toggleClass('btn-danger');
+
+ tr.parents('tr').prev().find('td.delete-control > button.btn-warning').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) {
+ // if any child row is not selected, toggle parent button to warning
+ tr.parents('tr').prev().find('td.delete-control > button.btn-danger').addClass('btn-warning').removeClass('btn-danger');
+ }
+ });
+ });
+}
+
diff --git a/data/interfaces/default/js/tables/history_table_modal.js b/data/interfaces/default/js/tables/history_table_modal.js
index c4536867..1080bdc3 100644
--- a/data/interfaces/default/js/tables/history_table_modal.js
+++ b/data/interfaces/default/js/tables/history_table_modal.js
@@ -126,6 +126,7 @@ history_table_modal_options = {
$('.media-type-tooltip').tooltip();
$('.thumb-tooltip').popover({
html: true,
+ container: '#history-modal',
trigger: 'hover',
placement: 'right',
content: function () {
diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html
index ad0d7c1b..ef9bb1ea 100644
--- a/data/interfaces/default/settings.html
+++ b/data/interfaces/default/settings.html
@@ -84,6 +84,12 @@ available_notification_agents = notifiers.available_notification_agents()
Set your preferred time format. Click here to see the parameter list.
+
+
+
Group successive play history by the same user as a single entry in tables.
+
diff --git a/plexpy/__init__.py b/plexpy/__init__.py
index b7faf64c..038cf14d 100644
--- a/plexpy/__init__.py
+++ b/plexpy/__init__.py
@@ -366,7 +366,7 @@ def dbcheck():
# session_history table :: This is a history table which logs essential stream details
c_db.execute(
- 'CREATE TABLE IF NOT EXISTS session_history (id INTEGER PRIMARY KEY AUTOINCREMENT, '
+ 'CREATE TABLE IF NOT EXISTS session_history (id INTEGER PRIMARY KEY AUTOINCREMENT, reference_id INTEGER, '
'started INTEGER, stopped INTEGER, rating_key INTEGER, user_id INTEGER, user TEXT, '
'ip_address TEXT, paused_counter INTEGER DEFAULT 0, player TEXT, platform TEXT, machine_id TEXT, '
'parent_rating_key INTEGER, grandparent_rating_key INTEGER, media_type TEXT, view_offset INTEGER DEFAULT 0)'
@@ -624,6 +624,29 @@ def dbcheck():
logger.debug(u'User "Local" does not exist. Adding user.')
c_db.execute('INSERT INTO users (user_id, username) VALUES (0, "Local")')
+ # Upgrade session_history table from earlier versions
+ try:
+ c_db.execute('SELECT reference_id from session_history')
+ except sqlite3.OperationalError:
+ logger.debug(u"Altering database. Updating database table session_history.")
+ c_db.execute(
+ 'ALTER TABLE session_history ADD COLUMN reference_id INTEGER DEFAULT 0'
+ )
+ # Set reference_id to the first row where (user_id = previous row, rating_key != previous row) and user_id = user_id
+ c_db.execute(
+ 'UPDATE session_history ' \
+ 'SET reference_id = (SELECT (CASE \
+ WHEN (SELECT MIN(id) FROM session_history WHERE id > ( \
+ SELECT MAX(id) FROM session_history \
+ WHERE (user_id = t1.user_id AND rating_key <> t1.rating_key AND id < t1.id)) AND user_id = t1.user_id) IS NULL \
+ THEN (SELECT MIN(id) FROM session_history WHERE (user_id = t1.user_id)) \
+ ELSE (SELECT MIN(id) FROM session_history WHERE id > ( \
+ SELECT MAX(id) FROM session_history \
+ WHERE (user_id = t1.user_id AND rating_key <> t1.rating_key AND id < t1.id)) AND user_id = t1.user_id) END) ' \
+ 'FROM session_history AS t1 ' \
+ 'WHERE t1.id = session_history.id) '
+ )
+
conn_db.commit()
c_db.close()
diff --git a/plexpy/activity_processor.py b/plexpy/activity_processor.py
index ed926254..93c8f160 100644
--- a/plexpy/activity_processor.py
+++ b/plexpy/activity_processor.py
@@ -159,6 +159,36 @@ class ActivityProcessor(object):
# logger.debug(u"PlexPy ActivityProcessor :: Writing session_history transaction...")
self.db.action(query=query, args=args)
+ # Check if we should group the session, select the last two rows from the user
+ query = 'SELECT id, rating_key, user_id, reference_id FROM session_history \
+ WHERE user_id = ? ORDER BY id DESC LIMIT 2 '
+
+ args = [session['user_id']]
+
+ result = self.db.select(query=query, args=args)
+
+ new_session = {'id': result[0][0],
+ 'rating_key': result[0][1],
+ 'user_id': result[0][2],
+ 'reference_id': result[0][3]}
+
+ if len(result) == 1:
+ prev_session = None
+ else:
+ prev_session = {'id': result[1][0],
+ 'rating_key': result[1][1],
+ 'user_id': result[1][2],
+ 'reference_id': result[1][3]}
+
+ query = 'UPDATE session_history SET reference_id = ? WHERE id = ? '
+ # If rating_key is the same in the previous session, then set the reference_id to the previous row, else set the reference_id to the new id
+ if (prev_session is not None) and (prev_session['rating_key'] == new_session['rating_key']):
+ args = [prev_session['reference_id'], new_session['id']]
+ else:
+ args = [new_session['id'], new_session['id']]
+
+ self.db.action(query=query, args=args)
+
# logger.debug(u"PlexPy ActivityProcessor :: Successfully written history item, last id for session_history is %s"
# % last_id)
diff --git a/plexpy/config.py b/plexpy/config.py
index 43b0035b..c5e1c8a2 100644
--- a/plexpy/config.py
+++ b/plexpy/config.py
@@ -73,6 +73,7 @@ _CONFIG_DEFINITIONS = {
'GIT_BRANCH': (str, 'General', 'master'),
'GIT_PATH': (str, 'General', ''),
'GIT_USER': (str, 'General', 'drzoidberg33'),
+ 'GROUP_HISTORY_TABLES': (int, 'General', 0),
'GROWL_ENABLED': (int, 'Growl', 0),
'GROWL_HOST': (str, 'Growl', ''),
'GROWL_PASSWORD': (str, 'Growl', ''),
diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py
index 576f64d6..421bc85e 100644
--- a/plexpy/datafactory.py
+++ b/plexpy/datafactory.py
@@ -26,48 +26,48 @@ class DataFactory(object):
def __init__(self):
pass
- def get_history(self, kwargs=None, custom_where=None):
+ def get_history(self, kwargs=None, custom_where=None, grouping=0, watched_percent=85):
data_tables = datatables.DataTables()
+
+ group_by = ['session_history.reference_id'] if grouping else ['session_history.id']
- columns = ['session_history.id',
- 'session_history.started as date',
- '(CASE WHEN users.friendly_name IS NULL THEN session_history'
- '.user ELSE users.friendly_name END) as friendly_name',
- 'session_history.player',
- 'session_history.ip_address',
- 'session_history_metadata.full_title as full_title',
+ columns = ['session_history.reference_id',
+ 'session_history.id',
+ 'started AS date',
+ 'MIN(started) AS started',
+ 'MAX(stopped) AS stopped',
+ 'SUM(CASE WHEN stopped > 0 THEN (stopped - started) ELSE 0 END) - \
+ SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS duration',
+ 'SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS paused_counter',
+ 'session_history.user_id',
+ 'session_history.user',
+ '(CASE WHEN users.friendly_name IS NULL THEN user ELSE users.friendly_name END) as friendly_name',
+ 'player',
+ 'ip_address',
+ 'session_history_metadata.media_type',
+ 'session_history_metadata.rating_key',
+ 'session_history_metadata.parent_rating_key',
+ 'session_history_metadata.grandparent_rating_key',
+ 'session_history_metadata.full_title',
+ 'session_history_metadata.parent_title',
+ 'session_history_metadata.year',
+ 'session_history_metadata.media_index',
+ 'session_history_metadata.parent_media_index',
'session_history_metadata.thumb',
'session_history_metadata.parent_thumb',
'session_history_metadata.grandparent_thumb',
- 'session_history_metadata.media_index',
- 'session_history_metadata.parent_media_index',
- 'session_history_metadata.parent_title',
- 'session_history_metadata.year',
- 'session_history.started',
- 'session_history.paused_counter',
- 'session_history.stopped',
- 'round((julianday(datetime(session_history.stopped, "unixepoch", "localtime")) - \
- julianday(datetime(session_history.started, "unixepoch", "localtime"))) * 86400) - \
- (CASE WHEN session_history.paused_counter IS NULL THEN 0 \
- ELSE session_history.paused_counter END) as duration',
- '((CASE WHEN session_history.view_offset IS NULL THEN 0.1 ELSE \
- session_history.view_offset * 1.0 END) / \
- (CASE WHEN session_history_metadata.duration IS NULL THEN 1.0 ELSE \
- session_history_metadata.duration * 1.0 END) * 100) as percent_complete',
- 'session_history.grandparent_rating_key as grandparent_rating_key',
- 'session_history.parent_rating_key as parent_rating_key',
- 'session_history.rating_key as rating_key',
- 'session_history.user',
- 'session_history_metadata.media_type',
+ '((CASE WHEN view_offset IS NULL THEN 0.1 ELSE view_offset * 1.0 END) / \
+ (CASE WHEN session_history_metadata.duration IS NULL THEN 1.0 ELSE session_history_metadata.duration * 1.0 END) * 100) AS percent_complete',
'session_history_media_info.video_decision',
'session_history_media_info.audio_decision',
- 'session_history.user_id as user_id'
+ 'COUNT(*) AS group_count',
+ 'GROUP_CONCAT(session_history.id) AS group_ids'
]
try:
query = data_tables.ssp_query(table_name='session_history',
columns=columns,
custom_where=custom_where,
- group_by=[],
+ group_by=group_by,
join_types=['LEFT OUTER JOIN',
'JOIN',
'JOIN'],
@@ -87,7 +87,7 @@ class DataFactory(object):
'error': 'Unable to execute database query.'}
history = query['result']
-
+
rows = []
for item in history:
if item["media_type"] == 'episode' and item["parent_thumb"]:
@@ -97,34 +97,44 @@ class DataFactory(object):
else:
thumb = item["thumb"]
- row = {"id": item['id'],
- "date": item['date'],
- "friendly_name": item['friendly_name'],
- "player": item["player"],
- "ip_address": item["ip_address"],
- "full_title": item["full_title"],
- "thumb": thumb,
- "media_index": item["media_index"],
- "parent_media_index": item["parent_media_index"],
- "parent_title": item["parent_title"],
- "year": item["year"],
+ if item['percent_complete'] >= watched_percent:
+ watched_status = 1
+ elif item['percent_complete'] >= watched_percent/2:
+ watched_status = 0.5
+ else:
+ watched_status = 0
+
+ row = {"reference_id": item["reference_id"],
+ "id": item["id"],
+ "date": item["date"],
"started": item["started"],
- "paused_counter": item["paused_counter"],
"stopped": item["stopped"],
"duration": item["duration"],
- "percent_complete": item["percent_complete"],
- "grandparent_rating_key": item["grandparent_rating_key"],
- "parent_rating_key": item["parent_rating_key"],
- "rating_key": item["rating_key"],
+ "paused_counter": item["paused_counter"],
+ "user_id": item["user_id"],
"user": item["user"],
+ "friendly_name": item["friendly_name"],
+ "player": item["player"],
+ "ip_address": item["ip_address"],
"media_type": item["media_type"],
+ "rating_key": item["rating_key"],
+ "parent_rating_key": item["parent_rating_key"],
+ "grandparent_rating_key": item["grandparent_rating_key"],
+ "full_title": item["full_title"],
+ "parent_title": item["parent_title"],
+ "year": item["year"],
+ "media_index": item["media_index"],
+ "parent_media_index": item["parent_media_index"],
+ "thumb": thumb,
"video_decision": item["video_decision"],
"audio_decision": item["audio_decision"],
- "user_id": item["user_id"]
+ "watched_status": watched_status,
+ "group_count": item["group_count"],
+ "group_ids": item["group_ids"]
}
rows.append(row)
-
+
dict = {'recordsFiltered': query['filteredCount'],
'recordsTotal': query['totalCount'],
'data': rows,
diff --git a/plexpy/webserve.py b/plexpy/webserve.py
index 30272681..46bb3dc2 100644
--- a/plexpy/webserve.py
+++ b/plexpy/webserve.py
@@ -458,7 +458,8 @@ class WebInterface(object):
"home_stats_cards": plexpy.CONFIG.HOME_STATS_CARDS,
"home_library_cards": plexpy.CONFIG.HOME_LIBRARY_CARDS,
"buffer_threshold": plexpy.CONFIG.BUFFER_THRESHOLD,
- "buffer_wait": plexpy.CONFIG.BUFFER_WAIT
+ "buffer_wait": plexpy.CONFIG.BUFFER_WAIT,
+ "group_history_tables": checked(plexpy.CONFIG.GROUP_HISTORY_TABLES)
}
return serve_template(templatename="settings.html", title="Settings", config=config)
@@ -474,7 +475,8 @@ class WebInterface(object):
"tv_notify_on_start", "movie_notify_on_start", "music_notify_on_start",
"tv_notify_on_stop", "movie_notify_on_stop", "music_notify_on_stop",
"tv_notify_on_pause", "movie_notify_on_pause", "music_notify_on_pause", "refresh_users_on_startup",
- "ip_logging_enable", "video_logging_enable", "music_logging_enable", "pms_is_remote", "home_stats_type"
+ "ip_logging_enable", "video_logging_enable", "music_logging_enable", "pms_is_remote", "home_stats_type",
+ "group_history_tables"
]
for checked_config in checked_configs:
if checked_config not in kwargs:
@@ -555,28 +557,38 @@ class WebInterface(object):
message=message, timer=timer, quote=quote)
@cherrypy.expose
- def get_history(self, user=None, user_id=None, **kwargs):
+ def get_history(self, user=None, user_id=None, grouping=0, **kwargs):
+
+ if grouping == 'false':
+ grouping = 0
+ else:
+ grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES
+
+ watched_percent = plexpy.CONFIG.NOTIFY_WATCHED_PERCENT
custom_where=[]
if user_id:
- custom_where = [['user_id', user_id]]
+ custom_where = [['session_history.user_id', user_id]]
elif user:
- custom_where = [['user', user]]
+ custom_where = [['session_history.user', user]]
if 'rating_key' in kwargs:
rating_key = kwargs.get('rating_key', "")
- custom_where = [['rating_key', rating_key]]
+ custom_where = [['session_history.rating_key', rating_key]]
if 'parent_rating_key' in kwargs:
rating_key = kwargs.get('parent_rating_key', "")
- custom_where = [['parent_rating_key', rating_key]]
+ custom_where = [['session_history.parent_rating_key', rating_key]]
if 'grandparent_rating_key' in kwargs:
rating_key = kwargs.get('grandparent_rating_key', "")
- custom_where = [['grandparent_rating_key', rating_key]]
+ custom_where = [['session_history.grandparent_rating_key', rating_key]]
if 'start_date' in kwargs:
start_date = kwargs.get('start_date', "")
custom_where = [['strftime("%Y-%m-%d", datetime(date, "unixepoch", "localtime"))', start_date]]
+ if 'reference_id' in kwargs:
+ reference_id = kwargs.get('reference_id', "")
+ custom_where = [['session_history.reference_id', reference_id]]
data_factory = datafactory.DataFactory()
- history = data_factory.get_history(kwargs=kwargs, custom_where=custom_where)
+ history = data_factory.get_history(kwargs=kwargs, custom_where=custom_where, grouping=grouping, watched_percent=watched_percent)
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps(history)
@@ -1428,4 +1440,4 @@ class WebInterface(object):
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps(result)
else:
- logger.warn('Unable to retrieve data.')
\ No newline at end of file
+ logger.warn('Unable to retrieve data.')