diff --git a/data/interfaces/default/js/tables/history_table.js b/data/interfaces/default/js/tables/history_table.js index 2ea85f02..7d8c819e 100644 --- a/data/interfaces/default/js/tables/history_table.js +++ b/data/interfaces/default/js/tables/history_table.js @@ -35,7 +35,11 @@ history_table_options = { "targets": [0], "data": null, "createdCell": function (td, cellData, rowData, row, col) { - $(td).html(''); + if (rowData['id'] === null) { + $(td).html(''); + } else { + $(td).html(''); + } }, "width": "5%", "className": "delete-control no-wrap hidden", @@ -46,14 +50,21 @@ history_table_options = { "targets": [1], "data":"date", "createdCell": function (td, cellData, rowData, row, col) { - if (rowData['stopped'] === null) { - $(td).html('Currently watching...'); + var date = moment(cellData, "X").format(date_format); + if (rowData['state'] !== null) { + var state = ''; + if (rowData['state'] === 'playing') { + state = ''; + } else if (rowData['state'] === 'paused') { + state = ''; + } else if (rowData['state'] === 'buffering') { + state = ''; + } + $(td).html('
' + state + ' ' + date + '
'); } else if (rowData['group_count'] > 1) { - date = moment(cellData, "X").format(date_format); expand_history = ''; $(td).html('
' + expand_history + ' ' + date + '
'); } else { - date = moment(cellData, "X").format(date_format); $(td).html('
 ' + date + '
'); } }, @@ -138,21 +149,22 @@ history_table_options = { var parent_info = ''; var media_type = ''; var thumb_popover = ''; + var source = (rowData['state'] === null) ? 'source=history&' : ''; if (rowData['media_type'] === 'movie') { if (rowData['year']) { parent_info = ' (' + rowData['year'] + ')'; } media_type = ''; thumb_popover = '' + cellData + parent_info + '' - $(td).html('
' + media_type + ' ' + thumb_popover + '
'); + $(td).html('
' + media_type + ' ' + thumb_popover + '
'); } else if (rowData['media_type'] === 'episode') { if (rowData['parent_media_index'] && rowData['media_index']) { parent_info = ' (S' + rowData['parent_media_index'] + '· E' + rowData['media_index'] + ')'; } media_type = ''; thumb_popover = '' + cellData + parent_info + '' - $(td).html('
' + media_type + ' ' + thumb_popover + '
'); + $(td).html('
' + media_type + ' ' + thumb_popover + '
'); } else if (rowData['media_type'] === 'track') { if (rowData['parent_title']) { parent_info = ' (' + rowData['parent_title'] + ')'; } media_type = ''; thumb_popover = '' + cellData + parent_info + '' - $(td).html('
' + media_type + ' ' + thumb_popover + '
'); + $(td).html('
' + media_type + ' ' + thumb_popover + '
'); } else { $(td).html('' + cellData + ''); } @@ -241,6 +253,7 @@ history_table_options = { $('#ajaxMsg').fadeOut(); // Create the tooltips. + $('.current-activity-tooltip').tooltip({ container: 'body' }); $('.expand-history-tooltip').tooltip({ container: 'body' }); $('.external-ip-tooltip').tooltip({ container: 'body' }); $('.transcode-tooltip').tooltip({ container: 'body' }); @@ -286,7 +299,7 @@ history_table_options = { if ($.inArray(rowData['id'], history_to_delete) !== -1) { $(row).find('button[data-id="' + rowData['id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger'); } - } else { + } else if (rowData['id'] !== null) { // if grouped rows // toggle the parent button to danger $(row).find('button[data-id="' + rowData['id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger'); @@ -318,7 +331,11 @@ $('.history_table').on('click', '> tbody > tr > td.modal-control', function () { function showStreamDetails() { $.ajax({ url: 'get_stream_data', - data: {row_id: rowData['id'], user: rowData['friendly_name']}, + data: { + row_id: rowData['id'], + session_key: rowData['session_key'], + user: rowData['friendly_name'] + }, cache: false, async: true, complete: function(xhr, status) { diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index 6c9a0c7c..264e1b98 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -57,47 +57,96 @@ class DataFactory(object): if not added: custom_where.append(['session_history.user_id', session.get_session_user_id()]) - group_by = ['session_history.reference_id'] if grouping else ['session_history.id'] + # Very hacky way to match the custom where parameters for the unioned table + custom_where_union = [[c[0].split('.')[-1], c[1]] for c in custom_where] - 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 OR TRIM(users.friendly_name) = "" \ - THEN users.username ELSE users.friendly_name END) AS friendly_name', - 'platform', - 'player', - 'ip_address', - 'session_history.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', - 'MAX((CASE WHEN (view_offset IS NULL OR view_offset = "") THEN 0.1 ELSE view_offset * 1.0 END) / \ - (CASE WHEN (session_history_metadata.duration IS NULL OR session_history_metadata.duration = "") \ - THEN 1.0 ELSE session_history_metadata.duration * 1.0 END) * 100) AS percent_complete', - 'session_history_media_info.transcode_decision', - 'COUNT(*) AS group_count', - 'GROUP_CONCAT(session_history.id) AS group_ids' - ] + group_by = ['session_history.reference_id'] if grouping else ['session_history.id'] + group_by_union = ['session_key'] + + 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 OR TRIM(users.friendly_name) = "" \ + THEN users.username ELSE users.friendly_name END) AS friendly_name', + 'platform', + 'player', + 'ip_address', + 'session_history.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', + 'MAX((CASE WHEN (view_offset IS NULL OR view_offset = "") THEN 0.1 ELSE view_offset * 1.0 END) / \ + (CASE WHEN (session_history_metadata.duration IS NULL OR session_history_metadata.duration = "") \ + THEN 1.0 ELSE session_history_metadata.duration * 1.0 END) * 100) AS percent_complete', + 'session_history_media_info.transcode_decision', + 'COUNT(*) AS group_count', + 'GROUP_CONCAT(session_history.id) AS group_ids', + 'NULL AS state', + 'NULL AS session_key' + ] + + columns_union = [ + 'NULL AS reference_id', + 'NULL AS id', + 'started AS date', + 'started', + 'stopped', + 'strftime("%s", "now") - started - \ + 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', + 'user_id', + 'user', + '(CASE WHEN friendly_name IS NULL OR TRIM(friendly_name) = "" \ + THEN user ELSE friendly_name END) AS friendly_name', + 'platform', + 'player', + 'ip_address', + 'media_type', + 'rating_key', + 'parent_rating_key', + 'grandparent_rating_key', + 'full_title', + 'parent_title', + 'year', + 'media_index', + 'parent_media_index', + 'thumb', + 'parent_thumb', + 'grandparent_thumb', + 'MAX((CASE WHEN (view_offset IS NULL OR view_offset = "") THEN 0.1 ELSE view_offset * 1.0 END) / \ + (CASE WHEN (duration IS NULL OR duration = "") \ + THEN 1.0 ELSE duration * 1.0 END) * 100) AS percent_complete', + 'transcode_decision', + 'NULL AS group_count', + 'NULL AS group_ids', + 'state', + 'session_key' + ] try: query = data_tables.ssp_query(table_name='session_history', + table_name_union='sessions', columns=columns, + columns_union=columns_union, custom_where=custom_where, + custom_where_union=custom_where_union, group_by=group_by, + group_by_union=group_by_union, join_types=['LEFT OUTER JOIN', 'JOIN', 'JOIN'], @@ -170,7 +219,9 @@ class DataFactory(object): 'percent_complete': int(round(item['percent_complete'])), 'watched_status': watched_status, 'group_count': item['group_count'], - 'group_ids': item['group_ids'] + 'group_ids': item['group_ids'], + 'state': item['state'], + 'session_key': item['session_key'] } rows.append(row) @@ -756,12 +807,13 @@ class DataFactory(object): return library_stats - def get_stream_details(self, row_id=None): + def get_stream_details(self, row_id=None, session_key=None): monitor_db = database.MonitorDatabase() user_cond = '' + table = 'session_history' if row_id else 'sessions' if session.get_session_user_id(): - user_cond = 'AND session_history.user_id = %s ' % session.get_session_user_id() + user_cond = 'AND %s.user_id = %s ' % (table, session.get_session_user_id()) if row_id: query = 'SELECT container, bitrate, video_resolution, width, height, aspect_ratio, video_framerate, ' \ @@ -773,6 +825,14 @@ class DataFactory(object): 'JOIN session_history_metadata ON session_history_media_info.id = session_history_metadata.id ' \ 'WHERE session_history_media_info.id = ? %s' % user_cond result = monitor_db.select(query, args=[row_id]) + elif session_key: + query = 'SELECT container, bitrate, video_resolution, width, height, aspect_ratio, video_framerate, ' \ + 'video_codec, audio_codec, audio_channels, video_decision, transcode_video_codec, transcode_height, ' \ + 'transcode_width, audio_decision, transcode_audio_codec, transcode_audio_channels, transcode_container, ' \ + 'media_type, title, grandparent_title ' \ + 'FROM sessions ' \ + 'WHERE session_key = ? %s' % user_cond + result = monitor_db.select(query, args=[session_key]) else: return None diff --git a/plexpy/datatables.py b/plexpy/datatables.py index 7c4bac4d..54b6ebbb 100644 --- a/plexpy/datatables.py +++ b/plexpy/datatables.py @@ -30,9 +30,13 @@ class DataTables(object): def ssp_query(self, table_name=None, + table_name_union=None, columns=[], + columns_union=[], custom_where=[], + custom_where_union=[], group_by=[], + group_by_union=[], join_types=[], join_tables=[], join_evals=[], @@ -46,10 +50,12 @@ class DataTables(object): parameters = {} args = [] group = '' + group_u = '' order = '' where = '' join = '' c_where = '' + c_where_u = '' # Fetch all our parameters if kwargs.get('json_data'): @@ -61,6 +67,7 @@ class DataTables(object): dt_columns = parameters['columns'] extracted_columns = self.extract_columns(columns=columns) + extracted_columns_union = self.extract_columns(columns=columns_union) # Build grouping if group_by: @@ -110,6 +117,48 @@ class DataTables(object): if c_where: c_where = 'WHERE ' + c_where.rstrip(' AND ') + # Build grouping union parameters + if group_by_union: + for g in group_by_union: + group_u += g + ', ' + if group_u: + group_u = 'GROUP BY ' + group_u.rstrip(', ') + else: + group_u = '' + + # Build custom where union parameters + if custom_where_union: + for w in custom_where_union: + if isinstance(w[1], (list, tuple)) and len(w[1]): + c_where_u += '(' + for w_ in w[1]: + if w_ == None: + c_where_u += w[0] + ' IS NULL OR ' + else: + c_where_u += w[0] + ' = ? OR ' + args.append(w_) + c_where_u = c_where_u.rstrip(' OR ') + ') AND ' + else: + if w[1] == None: + c_where_u += w[0] + ' IS NULL AND ' + else: + c_where_u += w[0] + ' = ? AND ' + args.append(w[1]) + + if c_where_u: + c_where_u = 'WHERE ' + c_where_u.rstrip(' AND ') + else: + c_where_u = '' + + # Build union parameters + if table_name_union: + union = 'UNION SELECT %s FROM %s %s %s' % (extracted_columns_union['column_string'], + table_name_union, + c_where_u, + group_u) + else: + union = '' + # Build ordering for o in parameters['order']: sort_order = ' COLLATE NOCASE' @@ -163,28 +212,31 @@ class DataTables(object): # Build our queries if grouping: if c_where == '': - query = 'SELECT * FROM (SELECT %s FROM %s %s %s) %s %s' \ - % (extracted_columns['column_string'], table_name, join, group, + query = 'SELECT * FROM (SELECT %s FROM %s %s %s %s) %s %s' \ + % (extracted_columns['column_string'], table_name, join, group, union, where, order) else: - query = 'SELECT * FROM (SELECT %s FROM %s %s %s %s) %s %s' \ - % (extracted_columns['column_string'], table_name, join, c_where, group, + query = 'SELECT * FROM (SELECT %s FROM %s %s %s %s %s) %s %s' \ + % (extracted_columns['column_string'], table_name, join, c_where, group, union, where, order) else: if c_where == '': - query = 'SELECT %s FROM %s %s %s %s' \ - % (extracted_columns['column_string'], table_name, join, where, - order) + query = 'SELECT * FROM (SELECT %s FROM %s %s %s) %s %s' \ + % (extracted_columns['column_string'], table_name, join, union, + where, order) else: - query = 'SELECT * FROM (SELECT %s FROM %s %s %s %s) %s' \ - % (extracted_columns['column_string'], table_name, join, where, - order, c_where) + query = 'SELECT * FROM (SELECT %s FROM %s %s %s %s) %s %s' \ + % (extracted_columns['column_string'], table_name, join, c_where, union, + where, order) # logger.debug(u"Query: %s" % query) # Execute the query filtered = self.ssp_db.select(query, args=args) + # Remove NULL rows + filtered = [row for row in filtered if not all(v is None for v in row.values())] + # Build grand totals totalcount = self.ssp_db.select('SELECT COUNT(id) as total_count from %s' % table_name)[0]['total_count'] diff --git a/plexpy/webserve.py b/plexpy/webserve.py index f039db95..c2d008bd 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -1702,10 +1702,10 @@ class WebInterface(object): @cherrypy.expose @requireAuth() - def get_stream_data(self, row_id=None, user=None, **kwargs): + def get_stream_data(self, row_id=None, session_key=None, user=None, **kwargs): data_factory = datafactory.DataFactory() - stream_data = data_factory.get_stream_details(row_id) + stream_data = data_factory.get_stream_details(row_id, session_key) return serve_template(templatename="stream_data.html", title="Stream Data", data=stream_data, user=user)