diff --git a/CHANGELOG.md b/CHANGELOG.md index 49b37825..625cca89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,33 @@ # Changelog +## v2.2.2-beta (2020-04-12) + +* Notifications: + * New: Added notification trigger for Tautulli database corruption. + * New: Added TAUTULLI_PYTHON_VERSION to script notification environment variables. + * Fix: Notification grouping by season/album and show/artist not enabled by default. + * Change: The file size notification parameter is now reported in SI units. (Thanks @aaronldunlap) +* UI: + * Fix: Delete lookup info from the media info page failing. + * New: Added icon on the users table to indicate if the user is not on the Plex server. + * New: Added icon on the libraries table to indicate if the library is not on the Plex server. + * Fix: XBMC platform icon not being redirected to the Kodi platform icon. + * Change: Improved deleting libraries so libraries with the same section ID are not also deleted. +* API: + * Fix: Returning XML for the API failing due to unicode characters. + * Fix: Grouping parameter for various API commands not falling back to default setting. + * New: Added time_queries parameter to get_library_watch_time_stats and get_user_watch_time_stats API command. (Thanks @KaasKop97) + * New: Added an "is_active" return value to the get_user, get_users, get_library, and get_libraries API commands which indicates if the user or library is on the Plex server. + * New: Added delete_history API command. + * Change: Added optional parameter for row_ids for delete_library, delete_user, delete_all_library_history, and delete_all_user_history API commands. +* Mobile App: + * Fix: Temporary device token not being invalidated after cancelling device registration. +* Other: + * Fix: Update failing on CentOS due to an older git version. + * Fix: Manifest file for creating a web app had incorrect info. + * New: Docker images updated to support ARM platforms. + + ## v2.2.1 (2020-03-28) * Notifications: diff --git a/data/interfaces/default/libraries.html b/data/interfaces/default/libraries.html index f4983904..d7360bf3 100644 --- a/data/interfaces/default/libraries.html +++ b/data/interfaces/default/libraries.html @@ -131,17 +131,6 @@ $('#confirm-modal-delete').modal(); $('#confirm-modal-delete').one('click', '#confirm-delete', function () { - $.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); - } - }); $.ajax({ url: 'delete_all_library_history', type: 'POST', @@ -151,9 +140,21 @@ success: function (data) { var msg = "Library history purged"; showMsg(msg, false, true, 2000); + libraries_list_table.draw(); + } + }); + $.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_list_table.draw(); } }); - libraries_list_table.draw(); }); } diff --git a/data/interfaces/default/users.html b/data/interfaces/default/users.html index f3595148..4d471d03 100644 --- a/data/interfaces/default/users.html +++ b/data/interfaces/default/users.html @@ -134,17 +134,6 @@ $('#confirm-modal-delete').modal(); $('#confirm-modal-delete').one('click', '#confirm-delete', function () { - $.ajax({ - url: 'delete_user', - type: 'POST', - data: { row_ids: users_to_delete.join(',') }, - cache: false, - async: true, - success: function (data) { - var msg = "User deleted"; - showMsg(msg, false, true, 2000); - } - }); $.ajax({ url: 'delete_all_user_history', type: 'POST', @@ -154,9 +143,21 @@ success: function (data) { var msg = "User history purged"; showMsg(msg, false, true, 2000); + users_list_table.draw(); + } + }); + $.ajax({ + url: 'delete_user', + type: 'POST', + data: { row_ids: users_to_delete.join(',') }, + cache: false, + async: true, + success: function (data) { + var msg = "User deleted"; + showMsg(msg, false, true, 2000); + users_list_table.draw(); } }); - users_list_table.draw(); }); } diff --git a/plexpy/api2.py b/plexpy/api2.py index 6066ceb1..5fa203e4 100644 --- a/plexpy/api2.py +++ b/plexpy/api2.py @@ -136,7 +136,7 @@ class API2(object): self._api_app = True if plexpy.CONFIG.API_ENABLED and not self._api_msg or self._api_cmd in ('get_apikey', 'docs', 'docs_md'): - if self._api_apikey == plexpy.CONFIG.API_KEY or (self._api_app and self._api_apikey == mobile_app.TEMP_DEVICE_TOKEN): + if self._api_apikey == plexpy.CONFIG.API_KEY or (self._api_app and self._api_apikey == mobile_app.get_temp_device_token()): self._api_authenticated = True elif self._api_app and mobile_app.get_mobile_device_by_token(self._api_apikey): @@ -420,7 +420,7 @@ class API2(object): if result: self._api_msg = 'Device registration successful.' self._api_result_type = 'success' - mobile_app.TEMP_DEVICE_TOKEN = None + mobile_app.set_temp_device_token(None) else: self._api_msg = 'Device registartion failed: database error.' self._api_result_type = 'error' diff --git a/plexpy/database.py b/plexpy/database.py index ae04286e..0d6637a2 100644 --- a/plexpy/database.py +++ b/plexpy/database.py @@ -70,18 +70,26 @@ def delete_rows_from_table(table, row_ids): if row_ids and isinstance(row_ids, str): row_ids = list(map(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) + if row_ids: + 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() + + try: + monitor_db.action(query, row_ids) + return True + except Exception as e: + logger.error("Tautulli Database :: Failed to delete rows from %s database table: %s" % (table, e)) + return False + + return True 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 + success = [] + for table in ('session_history', 'session_history_media_info', 'session_history_metadata'): + success.append(delete_rows_from_table(table=table, row_ids=row_ids)) + return all(success) def delete_user_history(user_id=None): @@ -97,19 +105,18 @@ def delete_user_history(user_id=None): 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(): +def delete_library_history(section_id=None): + if str(section_id).isdigit(): monitor_db = MonitorDatabase() - # Get all history associated with the server_id and section_id + # Get all history associated with the 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]) + 'WHERE session_history_metadata.section_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)) + logger.info("Tautulli Database :: Deleting all history for library section_id %s from database." % section_id) return delete_session_history_rows(row_ids=row_ids) diff --git a/plexpy/libraries.py b/plexpy/libraries.py index d77b4632..16f33bda 100644 --- a/plexpy/libraries.py +++ b/plexpy/libraries.py @@ -1058,9 +1058,16 @@ class Libraries(object): 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 server_id == plexpy.CONFIG.PMS_IDENTIFIER: + delete_success = database.delete_library_history(section_id=section_id) + else: + logger.warn("Tautulli Libraries :: Library history not deleted for library section_id %s " + "because library server_id %s does not match Plex server identifier %s." + % (section_id, server_id, plexpy.CONFIG.PMS_IDENTIFIER)) + delete_success = True + if purge_only: - return True + return delete_success else: logger.info("Tautulli Libraries :: Deleting library with server_id %s and section_id %s from database." % (server_id, section_id)) @@ -1068,7 +1075,7 @@ class Libraries(object): 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 + return delete_success except Exception as e: logger.warn("Tautulli Libraries :: Unable to execute database query for delete: %s." % e) diff --git a/plexpy/mobile_app.py b/plexpy/mobile_app.py index d9655ac5..3de44044 100644 --- a/plexpy/mobile_app.py +++ b/plexpy/mobile_app.py @@ -18,7 +18,7 @@ from __future__ import unicode_literals from future.builtins import str -import time +import threading import plexpy if plexpy.PYTHON2: @@ -32,6 +32,24 @@ else: TEMP_DEVICE_TOKEN = None +INVALIDATE_TIMER = None + + +def set_temp_device_token(token=None): + global TEMP_DEVICE_TOKEN + TEMP_DEVICE_TOKEN = token + + if TEMP_DEVICE_TOKEN is not None: + global INVALIDATE_TIMER + if INVALIDATE_TIMER: + INVALIDATE_TIMER.cancel() + invalidate_time = 5 * 60 # 5 minutes + INVALIDATE_TIMER = threading.Timer(invalidate_time, set_temp_device_token, args=[None]) + INVALIDATE_TIMER.start() + + +def get_temp_device_token(): + return TEMP_DEVICE_TOKEN def get_mobile_devices(device_id=None, device_token=None): diff --git a/plexpy/users.py b/plexpy/users.py index 592e1382..d19bfe02 100644 --- a/plexpy/users.py +++ b/plexpy/users.py @@ -698,9 +698,10 @@ class Users(object): return all(success) elif str(user_id).isdigit(): - database.delete_user_history(user_id=user_id) + delete_success = database.delete_user_history(user_id=user_id) + if purge_only: - return True + return delete_success else: logger.info("Tautulli Users :: Deleting user with user_id %s from database." % user_id) @@ -708,7 +709,7 @@ class Users(object): monitor_db.action('UPDATE users ' 'SET deleted_user = 1, keep_history = 0, do_notify = 0 ' 'WHERE user_id = ?', [user_id]) - return True + return delete_success except Exception as e: logger.warn("Tautulli Users :: Unable to execute database query for delete: %s." % e) diff --git a/plexpy/version.py b/plexpy/version.py index 70475936..3538f031 100644 --- a/plexpy/version.py +++ b/plexpy/version.py @@ -18,4 +18,4 @@ from __future__ import unicode_literals PLEXPY_BRANCH = "python3" -PLEXPY_RELEASE_VERSION = "v2.2.1" +PLEXPY_RELEASE_VERSION = "v2.2.2-beta" diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 701fa794..964df164 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -3674,12 +3674,12 @@ class WebInterface(object): @requireAuth(member_of("admin")) def verify_mobile_device(self, device_token='', cancel=False, **kwargs): if helpers.bool_true(cancel): - mobile_app.TEMP_DEVICE_TOKEN = None + mobile_app.set_temp_device_token(None) return {'result': 'error', 'message': 'Device registration cancelled.'} result = mobile_app.get_mobile_device_by_token(device_token) if result: - mobile_app.TEMP_DEVICE_TOKEN = None + mobile_app.set_temp_device_token(None) return {'result': 'success', 'message': 'Device registered successfully.', 'data': result} else: return {'result': 'error', 'message': 'Device not registered.'} @@ -3966,7 +3966,7 @@ class WebInterface(object): logger._BLACKLIST_WORDS.add(apikey) if helpers.bool_true(device): - mobile_app.TEMP_DEVICE_TOKEN = apikey + mobile_app.set_temp_device_token(apikey) return apikey