diff --git a/data/interfaces/default/js/tables/libraries.js b/data/interfaces/default/js/tables/libraries.js index 903d5756..37be26ca 100644 --- a/data/interfaces/default/js/tables/libraries.js +++ b/data/interfaces/default/js/tables/libraries.js @@ -27,8 +27,8 @@ libraries_list_table_options = { "data": null, "createdCell": function (td, cellData, rowData, row, col) { $(td).html('
' + - ' ' + - '   ' + + ' ' + + '   ' + ' ' + '
'); }, @@ -63,7 +63,7 @@ libraries_list_table_options = { "data": "section_name", "createdCell": function (td, cellData, rowData, row, col) { if (cellData !== null && cellData !== '') { - $(td).html('
' + + $(td).html('
' + '' + cellData + '' + '
'); } else { @@ -234,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'); } } } @@ -279,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(); } @@ -302,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) { diff --git a/data/interfaces/default/libraries.html b/data/interfaces/default/libraries.html index 4595dce4..f4983904 100644 --- a/data/interfaces/default/libraries.html +++ b/data/interfaces/default/libraries.html @@ -116,14 +116,14 @@ }); if (libraries_to_delete.length > 0) { - $('#libraries-to-delete').prepend('

Are you REALLY sure you want to delete the following libraries:

') + $('#libraries-to-delete').prepend('

Are you REALLY sure you want to delete the following libraries:

'); for (var i = 0; i < libraries_to_delete.length; i++) { $('#libraries-to-delete').append('
  • ' + $('div[data-id=' + libraries_to_delete[i] + ']').text() + '
  • '); } } if (libraries_to_purge.length > 0) { - $('#libraries-to-purge').prepend('

    Are you REALLY sure you want to purge all history for the following libraries:

    ') + $('#libraries-to-purge').prepend('

    Are you REALLY sure you want to purge all history for the following libraries:

    '); for (var i = 0; i < libraries_to_purge.length; i++) { $('#libraries-to-purge').append('
  • ' + $('div[data-id=' + libraries_to_purge[i] + ']').text() + '
  • '); } @@ -131,31 +131,27 @@ $('#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 }, - cache: false, - async: true, - success: function (data) { - var msg = "Library deleted"; - showMsg(msg, false, true, 2000); - } - }); + $.ajax({ + url: 'delete_library', + type: 'POST', + data: { row_ids: libraries_to_delete.join(',') }, + cache: false, + async: true, + success: function (data) { + var msg = "Library deleted"; + showMsg(msg, false, true, 2000); + } }); - libraries_to_purge.forEach(function(row, idx) { - $.ajax({ - url: 'delete_all_library_history', - type: 'POST', - data: { section_id: row }, - cache: false, - async: true, - success: function (data) { - var msg = "Library history purged"; - showMsg(msg, false, true, 2000); - } - }); + $.ajax({ + url: 'delete_all_library_history', + type: 'POST', + data: { row_ids: libraries_to_purge.join(',') }, + cache: false, + async: true, + success: function (data) { + var msg = "Library history purged"; + 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(' ' + msg, false, true, 2000, false); libraries_list_table.draw(); } else { diff --git a/plexpy/database.py b/plexpy/database.py index b55deaab..b88b190a 100644 --- a/plexpy/database.py +++ b/plexpy/database.py @@ -81,10 +81,27 @@ def delete_user_history(user_id=None): 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]) + result = monitor_db.select('SELECT id FROM session_history WHERE user_id = ?', + [user_id]) row_ids = [row['id'] for row in result] - logger.info(u"Tautulli Database :: Deleting all history for user id %s from database." % user_id) + logger.info(u"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(u"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) diff --git a/plexpy/libraries.py b/plexpy/libraries.py index 141ac0fe..79e520ba 100644 --- a/plexpy/libraries.py +++ b/plexpy/libraries.py @@ -298,7 +298,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', @@ -312,7 +314,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', @@ -371,7 +373,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'], @@ -382,7 +386,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'], @@ -736,7 +740,9 @@ class Libraries(object): logger.warn(u"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, @@ -759,7 +765,8 @@ 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, is_active, ' \ 'do_notify, do_notify_created, keep_history, deleted_section ' \ @@ -787,7 +794,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, @@ -1015,60 +1024,39 @@ 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(u"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 server_id and str(section_id).isdigit(): + 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(u"Tautulli Libraries :: Unable to execute database query for delete_all_history: %s." % e) + logger.info(u"Tautulli Libraries :: Deleting library with server_id %s and section_id %s from database." + % (server_id, section_id)) + try: + 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(u"Tautulli Libraries :: Unable to execute database query for delete: %s." % e) - def delete(self, section_id=None): - monitor_db = database.MonitorDatabase() - - try: - if section_id.isdigit(): - self.delete_all_history(section_id) - logger.info(u"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.' - except Exception as e: - logger.warn(u"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() diff --git a/plexpy/users.py b/plexpy/users.py index 20d9029f..cf61db0c 100644 --- a/plexpy/users.py +++ b/plexpy/users.py @@ -684,7 +684,8 @@ class Users(object): if purge_only: return True else: - logger.info(u"Tautulli Users :: Deleting user with user_id %s from database." % user_id) + logger.info(u"Tautulli Users :: Deleting user with user_id %s from database." + % user_id) try: monitor_db.action('UPDATE users ' 'SET deleted_user = 1, keep_history = 0, do_notify = 0 ' diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 2b295ef0..6292e1fa 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -397,7 +397,7 @@ 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": [], @@ -414,9 +414,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 }, @@ -794,9 +796,11 @@ class WebInterface(object): "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" } ``` """ @@ -906,57 +910,59 @@ 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. ``` Required parameters: + server_id (str): The Plex server identifier of the library section section_id (str): The id of the Plex library section Optional parameters: - None + row_ids (str): Comma separated row ids to delete, e.g. "2,3,8" Returns: None ``` """ - 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} + if (server_id and section_id) or row_ids: + library_data = libraries.Libraries() + 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 {'result': 'error', 'message': 'Failed to delete library(s) history.'} else: - return {'message': 'no data received'} + 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. ``` Required parameters: + server_id (str): The Plex server identifier of the library section section_id (str): The id of the Plex library section Optional parameters: - None + row_ids (str): Comma separated row ids to delete, e.g. "2,3,8" Returns: None ``` """ - library_data = libraries.Libraries() - - if section_id: - delete_row = library_data.delete(section_id=section_id) - - if delete_row: - return {'message': delete_row} + if (server_id and section_id) or row_ids: + library_data = libraries.Libraries() + 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 {'result': 'error', 'message': 'Failed to delete library(s).'} else: - return {'message': 'no data received'} + return {'result': 'error', 'message': 'No server id and section id or row ids received.'} @cherrypy.expose @cherrypy.tools.json_out() @@ -1291,7 +1297,7 @@ class WebInterface(object): "data": [{"friendly_name": "Jon Snow", "guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en", - "history_row_id": 1121, + "id": 1121, "ip_address": "xxx.xxx.xxx.xxx", "last_played": "Game of Thrones - The Red Woman", "last_seen": 1462591869,