From d1f1763919a7d2d4fc27895a1237cbd5a540417e Mon Sep 17 00:00:00 2001 From: Mitch Date: Sun, 5 Apr 2020 20:03:57 +0200 Subject: [PATCH 01/26] Allow custom time_queries for get_watch_stats --- API.md | 2 ++ plexpy/libraries.py | 8 ++++++-- plexpy/users.py | 8 ++++++-- plexpy/webserve.py | 11 +++++++---- 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/API.md b/API.md index 8164bd97..8cb7ed60 100644 --- a/API.md +++ b/API.md @@ -1066,6 +1066,7 @@ Required parameters: Optional parameters: grouping (int): 0 or 1 + time_queries (str): "1, 7, 30, 0" Returns: json: @@ -2378,6 +2379,7 @@ Required parameters: Optional parameters: grouping (int): 0 or 1 + time_queries (str): "1, 7, 30, 0" Returns: json: diff --git a/plexpy/libraries.py b/plexpy/libraries.py index 14fa7d22..e2e85595 100644 --- a/plexpy/libraries.py +++ b/plexpy/libraries.py @@ -812,16 +812,20 @@ class Libraries(object): # If there is no library data we must return something return default_return - def get_watch_time_stats(self, section_id=None, grouping=None): + def get_watch_time_stats(self, section_id=None, grouping=None, time_queries=None): if not session.allow_session_library(section_id): return [] if grouping is None: grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES + if time_queries is not None: + time_queries = map(int, time_queries.split(",")) + else: + time_queries = [1, 7, 30, 0] + monitor_db = database.MonitorDatabase() - time_queries = [1, 7, 30, 0] library_watch_time_stats = [] group_by = 'session_history.reference_id' if grouping else 'session_history.id' diff --git a/plexpy/users.py b/plexpy/users.py index 35e19201..e8a63392 100644 --- a/plexpy/users.py +++ b/plexpy/users.py @@ -434,16 +434,20 @@ class Users(object): # Use "Local" user to retain compatibility with PlexWatch database value return default_return - def get_watch_time_stats(self, user_id=None, grouping=None): + def get_watch_time_stats(self, user_id=None, grouping=None, time_queries=None): if not session.allow_session_user(user_id): return [] if grouping is None: grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES + if time_queries is not None: + time_queries = map(int, time_queries.split(",")) + else: + time_queries = [1, 7, 30, 0] + monitor_db = database.MonitorDatabase() - time_queries = [1, 7, 30, 0] user_watch_time_stats = [] group_by = 'reference_id' if grouping else 'id' diff --git a/plexpy/webserve.py b/plexpy/webserve.py index e38c509e..9b9b6729 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -810,7 +810,7 @@ class WebInterface(object): @cherrypy.tools.json_out() @requireAuth(member_of("admin")) @addtoapi() - def get_library_watch_time_stats(self, section_id=None, grouping=None, **kwargs): + def get_library_watch_time_stats(self, section_id=None, grouping=None, time_queries=None, **kwargs): """ Get a library's watch time statistics. ``` @@ -819,6 +819,7 @@ class WebInterface(object): Optional parameters: grouping (int): 0 or 1 + time_queries (str): "1, 7, 30, 0" Returns: json: @@ -845,7 +846,8 @@ class WebInterface(object): if section_id: library_data = libraries.Libraries() - result = library_data.get_watch_time_stats(section_id=section_id, grouping=grouping) + result = library_data.get_watch_time_stats(section_id=section_id, grouping=grouping, + time_queries=time_queries) if result: return result else: @@ -1427,7 +1429,7 @@ class WebInterface(object): @cherrypy.tools.json_out() @requireAuth(member_of("admin")) @addtoapi() - def get_user_watch_time_stats(self, user_id=None, grouping=None, **kwargs): + def get_user_watch_time_stats(self, user_id=None, grouping=None, time_queries=None, **kwargs): """ Get a user's watch time statistics. ``` @@ -1436,6 +1438,7 @@ class WebInterface(object): Optional parameters: grouping (int): 0 or 1 + time_queries (str): "1, 7, 30, 0" Returns: json: @@ -1462,7 +1465,7 @@ class WebInterface(object): if user_id: user_data = users.Users() - result = user_data.get_watch_time_stats(user_id=user_id, grouping=grouping) + result = user_data.get_watch_time_stats(user_id=user_id, grouping=grouping, time_queries=time_queries) if result: return result else: From ae9df92d287e13566c817d61a7f7ab54c35b3502 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Wed, 8 Apr 2020 22:55:56 -0700 Subject: [PATCH 02/26] Divide file size by 2^10 but display SI units --- data/interfaces/default/js/script.js | 3 ++- plexpy/helpers.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/data/interfaces/default/js/script.js b/data/interfaces/default/js/script.js index 089488c9..b03bb3ee 100644 --- a/data/interfaces/default/js/script.js +++ b/data/interfaces/default/js/script.js @@ -462,7 +462,8 @@ $('*').on('click', '.refresh_pms_image', function (e) { // Taken from http://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable#answer-14919494 function humanFileSize(bytes, si = true) { - var thresh = si ? 1000 : 1024; + //var thresh = si ? 1000 : 1024; + var thresh = 1024; // Always divide by 2^10 but display SI units if (Math.abs(bytes) < thresh) { return bytes + ' B'; } diff --git a/plexpy/helpers.py b/plexpy/helpers.py index 4ed03e45..a2adb592 100644 --- a/plexpy/helpers.py +++ b/plexpy/helpers.py @@ -1034,7 +1034,8 @@ def humanFileSize(bytes, si=True): else: return bytes - thresh = 1000 if si else 1024 + #thresh = 1000 if si else 1024 + thresh = 1024 # Always divide by 2^10 but display SI units if bytes < thresh: return str(bytes) + ' B' From 1d08069162158e45431985e3e5836839f3730716 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 9 Apr 2020 18:30:46 -0700 Subject: [PATCH 03/26] Rename time_queries to query_days --- API.md | 4 ++-- plexpy/libraries.py | 10 +++++----- plexpy/users.py | 10 +++++----- plexpy/webserve.py | 12 ++++++------ 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/API.md b/API.md index 8cb7ed60..ae147c46 100644 --- a/API.md +++ b/API.md @@ -1066,7 +1066,7 @@ Required parameters: Optional parameters: grouping (int): 0 or 1 - time_queries (str): "1, 7, 30, 0" + query_days (str): Comma separated days, e.g. "1, 7, 30, 0" Returns: json: @@ -2379,7 +2379,7 @@ Required parameters: Optional parameters: grouping (int): 0 or 1 - time_queries (str): "1, 7, 30, 0" + query_days (str): Comma separated days, e.g. "1, 7, 30, 0" Returns: json: diff --git a/plexpy/libraries.py b/plexpy/libraries.py index e2e85595..9f5136d9 100644 --- a/plexpy/libraries.py +++ b/plexpy/libraries.py @@ -812,17 +812,17 @@ class Libraries(object): # If there is no library data we must return something return default_return - def get_watch_time_stats(self, section_id=None, grouping=None, time_queries=None): + def get_watch_time_stats(self, section_id=None, grouping=None, query_days=None): if not session.allow_session_library(section_id): return [] if grouping is None: grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES - if time_queries is not None: - time_queries = map(int, time_queries.split(",")) + if query_days is not None: + query_days = map(int, query_days.split(",")) else: - time_queries = [1, 7, 30, 0] + query_days = [1, 7, 30, 0] monitor_db = database.MonitorDatabase() @@ -830,7 +830,7 @@ class Libraries(object): group_by = 'session_history.reference_id' if grouping else 'session_history.id' - for days in time_queries: + for days in query_days: try: if days > 0: if str(section_id).isdigit(): diff --git a/plexpy/users.py b/plexpy/users.py index e8a63392..0a5f7621 100644 --- a/plexpy/users.py +++ b/plexpy/users.py @@ -434,17 +434,17 @@ class Users(object): # Use "Local" user to retain compatibility with PlexWatch database value return default_return - def get_watch_time_stats(self, user_id=None, grouping=None, time_queries=None): + def get_watch_time_stats(self, user_id=None, grouping=None, query_days=None): if not session.allow_session_user(user_id): return [] if grouping is None: grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES - if time_queries is not None: - time_queries = map(int, time_queries.split(",")) + if query_days is not None: + query_days = map(int, query_days.split(",")) else: - time_queries = [1, 7, 30, 0] + query_days = [1, 7, 30, 0] monitor_db = database.MonitorDatabase() @@ -452,7 +452,7 @@ class Users(object): group_by = 'reference_id' if grouping else 'id' - for days in time_queries: + for days in query_days: try: if days > 0: if str(user_id).isdigit(): diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 6145dfe9..a8e87aa4 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -812,7 +812,7 @@ class WebInterface(object): @cherrypy.tools.json_out() @requireAuth(member_of("admin")) @addtoapi() - def get_library_watch_time_stats(self, section_id=None, grouping=None, time_queries=None, **kwargs): + def get_library_watch_time_stats(self, section_id=None, grouping=None, query_days=None, **kwargs): """ Get a library's watch time statistics. ``` @@ -821,7 +821,7 @@ class WebInterface(object): Optional parameters: grouping (int): 0 or 1 - time_queries (str): "1, 7, 30, 0" + query_days (str): Comma separated days, e.g. "1, 7, 30, 0" Returns: json: @@ -849,7 +849,7 @@ class WebInterface(object): if section_id: library_data = libraries.Libraries() result = library_data.get_watch_time_stats(section_id=section_id, grouping=grouping, - time_queries=time_queries) + query_days=query_days) if result: return result else: @@ -1433,7 +1433,7 @@ class WebInterface(object): @cherrypy.tools.json_out() @requireAuth(member_of("admin")) @addtoapi() - def get_user_watch_time_stats(self, user_id=None, grouping=None, time_queries=None, **kwargs): + def get_user_watch_time_stats(self, user_id=None, grouping=None, query_days=None, **kwargs): """ Get a user's watch time statistics. ``` @@ -1442,7 +1442,7 @@ class WebInterface(object): Optional parameters: grouping (int): 0 or 1 - time_queries (str): "1, 7, 30, 0" + query_days (str): Comma separated days, e.g. "1, 7, 30, 0" Returns: json: @@ -1469,7 +1469,7 @@ class WebInterface(object): if user_id: user_data = users.Users() - result = user_data.get_watch_time_stats(user_id=user_id, grouping=grouping, time_queries=time_queries) + result = user_data.get_watch_time_stats(user_id=user_id, grouping=grouping, query_days=query_days) if result: return result else: From ca2b4085c9f7e94f6538e7ceda64b164677decc1 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 9 Apr 2020 18:33:02 -0700 Subject: [PATCH 04/26] Fix if bad query_days passed to API --- plexpy/libraries.py | 4 ++-- plexpy/users.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plexpy/libraries.py b/plexpy/libraries.py index 9f5136d9..91c27a33 100644 --- a/plexpy/libraries.py +++ b/plexpy/libraries.py @@ -819,8 +819,8 @@ class Libraries(object): if grouping is None: grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES - if query_days is not None: - query_days = map(int, query_days.split(",")) + if query_days and query_days is not None: + query_days = map(helpers.cast_to_int, query_days.split(',')) else: query_days = [1, 7, 30, 0] diff --git a/plexpy/users.py b/plexpy/users.py index 0a5f7621..e4b36cd5 100644 --- a/plexpy/users.py +++ b/plexpy/users.py @@ -441,8 +441,8 @@ class Users(object): if grouping is None: grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES - if query_days is not None: - query_days = map(int, query_days.split(",")) + if query_days and query_days is not None: + query_days = map(helpers.cast_to_int, query_days.split(',')) else: query_days = [1, 7, 30, 0] From ce289995ff54efdeddb29dda885b5d8082baa033 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 9 Apr 2020 23:15:08 -0700 Subject: [PATCH 05/26] Add user is_active to database --- plexpy/__init__.py | 19 ++++++++++++++----- plexpy/plextv.py | 2 ++ plexpy/users.py | 7 +++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 60bffef3..24b58f8e 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -663,11 +663,11 @@ def dbcheck(): c_db.execute( 'CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, ' 'user_id INTEGER DEFAULT NULL UNIQUE, username TEXT NOT NULL, friendly_name TEXT, ' - 'thumb TEXT, custom_avatar_url TEXT, email TEXT, is_admin INTEGER DEFAULT 0, is_home_user INTEGER DEFAULT NULL, ' - 'is_allow_sync INTEGER DEFAULT NULL, is_restricted INTEGER DEFAULT NULL, do_notify INTEGER DEFAULT 1, ' - 'keep_history INTEGER DEFAULT 1, deleted_user INTEGER DEFAULT 0, allow_guest INTEGER DEFAULT 0, ' - 'user_token TEXT, server_token TEXT, shared_libraries TEXT, filter_all TEXT, filter_movies TEXT, filter_tv TEXT, ' - 'filter_music TEXT, filter_photos TEXT)' + 'thumb TEXT, custom_avatar_url TEXT, email TEXT, is_active INTEGER DEFAULT 1, is_admin INTEGER DEFAULT 0, ' + 'is_home_user INTEGER DEFAULT NULL, is_allow_sync INTEGER DEFAULT NULL, is_restricted INTEGER DEFAULT NULL, ' + 'do_notify INTEGER DEFAULT 1, keep_history INTEGER DEFAULT 1, deleted_user INTEGER DEFAULT 0, ' + 'allow_guest INTEGER DEFAULT 0, user_token TEXT, server_token TEXT, shared_libraries TEXT, ' + 'filter_all TEXT, filter_movies TEXT, filter_tv TEXT, filter_music TEXT, filter_photos TEXT)' ) # library_sections table :: This table keeps record of the servers library sections @@ -1733,6 +1733,15 @@ def dbcheck(): 'ALTER TABLE users ADD COLUMN is_admin INTEGER DEFAULT 0' ) + # Upgrade users table from earlier versions + try: + c_db.execute('SELECT is_active FROM users') + except sqlite3.OperationalError: + logger.debug(u"Altering database. Updating database table users.") + c_db.execute( + 'ALTER TABLE users ADD COLUMN is_active INTEGER DEFAULT 1' + ) + # Upgrade notify_log table from earlier versions try: c_db.execute('SELECT poster_url FROM notify_log') diff --git a/plexpy/plextv.py b/plexpy/plextv.py index 1405a937..e389cf07 100644 --- a/plexpy/plextv.py +++ b/plexpy/plextv.py @@ -396,6 +396,7 @@ class PlexTV(object): "username": helpers.get_xml_attr(a, 'username'), "thumb": helpers.get_xml_attr(a, 'thumb'), "email": helpers.get_xml_attr(a, 'email'), + "is_active": 1, "is_admin": 1, "is_home_user": helpers.get_xml_attr(a, 'home'), "is_allow_sync": 1, @@ -423,6 +424,7 @@ class PlexTV(object): "username": helpers.get_xml_attr(a, 'title'), "thumb": helpers.get_xml_attr(a, 'thumb'), "email": helpers.get_xml_attr(a, 'email'), + "is_active": 1, "is_admin": 0, "is_home_user": helpers.get_xml_attr(a, 'home'), "is_allow_sync": helpers.get_xml_attr(a, 'allowSync'), diff --git a/plexpy/users.py b/plexpy/users.py index e4b36cd5..5c78e0c0 100644 --- a/plexpy/users.py +++ b/plexpy/users.py @@ -34,7 +34,11 @@ def refresh_users(): if result: monitor_db = database.MonitorDatabase() + # Keep track of user_id to update is_active status + user_ids = [0] # Local user always considered active + for item in result: + user_ids.append(helpers.cast_to_int(item['user_id'])) if item.get('shared_libraries'): item['shared_libraries'] = ';'.join(item['shared_libraries']) @@ -58,6 +62,9 @@ def refresh_users(): monitor_db.upsert('users', item, keys_dict) + query = 'UPDATE users SET is_active = 0 WHERE user_id NOT IN ({})'.format(', '.join(['?'] * len(user_ids))) + monitor_db.action(query=query, args=user_ids) + logger.info(u"Tautulli Users :: Users list refreshed.") return True else: From f366304c50fe2283b3b73338b559346a1659229b Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Fri, 10 Apr 2020 00:02:30 -0700 Subject: [PATCH 06/26] Show user active status on Users table --- data/interfaces/default/css/tautulli.css | 18 +++++++++++++++--- data/interfaces/default/js/tables/users.js | 6 ++++-- data/interfaces/default/user.html | 10 +++++++++- plexpy/users.py | 20 ++++++++++++++------ 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/data/interfaces/default/css/tautulli.css b/data/interfaces/default/css/tautulli.css index a3865497..59182e37 100644 --- a/data/interfaces/default/css/tautulli.css +++ b/data/interfaces/default/css/tautulli.css @@ -711,7 +711,6 @@ fieldset[disabled] .form-control { box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); } .users-poster-face { - overflow: hidden; float: left; background-size: cover; background-position: center; @@ -3119,6 +3118,20 @@ div.dataTables_info { font-weight: bold; border-radius: 2px; } +.inactive-library-tooltip, +.inactive-user-tooltip { + display: inline-block; + position: relative; + width: 100%; + height: 100%; +} +.inactive-library-tooltip i.fa, +.inactive-user-tooltip i.fa { + position: absolute; + right: 0; + bottom: 0; + text-shadow: 0 0 2px rgba(0,0,0,.5); +} .history-thumbnail-popover { z-index: 2000; padding: 0; @@ -3808,9 +3821,8 @@ a:hover .overlay-refresh-image:hover { } .svg-icon { - padding: 10px; + background-size: calc(100% - 20px) calc(100% - 20px) !important; background-origin: content-box !important; - background-size: contain !important; background-repeat: no-repeat !important; background-position: center !important; } diff --git a/data/interfaces/default/js/tables/users.js b/data/interfaces/default/js/tables/users.js index fdbc0449..58918d46 100644 --- a/data/interfaces/default/js/tables/users.js +++ b/data/interfaces/default/js/tables/users.js @@ -59,10 +59,12 @@ users_list_table_options = { "targets": [1], "data": "user_thumb", "createdCell": function (td, cellData, rowData, row, col) { + var inactive = ''; + if (!rowData['is_active']) { inactive = ''; } if (cellData === '') { - $(td).html('
'); + $(td).html('
' + inactive + '
'); } else { - $(td).html('
'); + $(td).html('
' + inactive + '
'); } }, "orderable": false, diff --git a/data/interfaces/default/user.html b/data/interfaces/default/user.html index d25cdf51..3e0bf406 100644 --- a/data/interfaces/default/user.html +++ b/data/interfaces/default/user.html @@ -51,7 +51,13 @@ DOCUMENTATION :: END