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('
' + expand_history + ' ' + date + '
'); } 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('
' + transcode_dec + ' ' + cellData + '
'); + } + $(td).html('
' + transcode_dec + ' ' + cellData + '
'); } }, "width": "15%", @@ -121,16 +127,16 @@ history_table_options = { if (rowData['media_type'] === 'movie') { media_type = ''; thumb_popover = '' + cellData + ' (' + rowData['year'] + ')' - $(td).html('
' + media_type + ' ' + thumb_popover + '
'); + $(td).html('
' + media_type + ' ' + thumb_popover + '
'); } else if (rowData['media_type'] === 'episode') { media_type = ''; thumb_popover = '' + cellData + ' \ (S' + rowData['parent_media_index'] + '· E' + rowData['media_index'] + ')' - $(td).html('
' + media_type + ' ' + thumb_popover + '
'); + $(td).html('
' + media_type + ' ' + thumb_popover + '
'); } else if (rowData['media_type'] === 'track') { media_type = ''; thumb_popover = '' + cellData + ' (' + rowData['parent_title'] + ')' - $(td).html('
' + media_type + ' ' + thumb_popover + '
'); + $(td).html('
' + media_type + ' ' + thumb_popover + '
'); } 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 '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
DeleteTimeUserIP AddressPlatformTitleStartedPausedStoppedDuration
' + + '
'; +} + +// 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.')