From a6b0cdef9740b6db5d8f230015a03bc9586f253e Mon Sep 17 00:00:00 2001 From: Mario Loria Date: Wed, 9 Dec 2015 11:00:53 -0500 Subject: [PATCH 01/25] add .pem cert to ignore list --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f3297290..3e58c483 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ cache/* *.crt *.key *.csr +*.pem # OS generated files # ###################### From 6e4198e7be41db30bc0d5f549f85be57380b22cc Mon Sep 17 00:00:00 2001 From: zobe123 Date: Thu, 10 Dec 2015 23:56:02 +0100 Subject: [PATCH 02/25] Update current_activity_header.html dynamic "s" at the word stream/streams added "no" before Activity when their is no activity --- .../default/current_activity_header.html | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/data/interfaces/default/current_activity_header.html b/data/interfaces/default/current_activity_header.html index 897a99af..3aa469b9 100644 --- a/data/interfaces/default/current_activity_header.html +++ b/data/interfaces/default/current_activity_header.html @@ -15,11 +15,15 @@ DOCUMENTATION :: END % if data != None: -% if data == '0': -

Activity

+ % if data == '0': +

no Activity

+ % else: + % if data == '1': +

Activity ${data} stream

+ % else: +

Activity ${data} streams

+ % endif + % endif % else: -

Activity ${data} stream(s)

+

no Activity

% endif -% else: -

Activity

-% endif \ No newline at end of file From 604155b41b67e146e90b2f618b9e77e5d01cb780 Mon Sep 17 00:00:00 2001 From: zobe123 Date: Fri, 11 Dec 2015 22:23:14 +0100 Subject: [PATCH 03/25] Update current_activity_header.html remove "no" --- data/interfaces/default/current_activity_header.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/interfaces/default/current_activity_header.html b/data/interfaces/default/current_activity_header.html index 3aa469b9..0727e558 100644 --- a/data/interfaces/default/current_activity_header.html +++ b/data/interfaces/default/current_activity_header.html @@ -16,7 +16,7 @@ DOCUMENTATION :: END % if data != None: % if data == '0': -

no Activity

+

Activity

% else: % if data == '1':

Activity ${data} stream

@@ -25,5 +25,5 @@ DOCUMENTATION :: END % endif % endif % else: -

no Activity

+

Activity

% endif From 5c9c2f9ab80c9af395e20a5ddb347d9cbcbe419e Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 11 Dec 2015 20:39:33 +0200 Subject: [PATCH 04/25] Fix greedy search bar. --- data/interfaces/default/base.html | 6 +++--- data/interfaces/default/css/plexpy.css | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/data/interfaces/default/base.html b/data/interfaces/default/base.html index 0d66fac7..129bea69 100644 --- a/data/interfaces/default/base.html +++ b/data/interfaces/default/base.html @@ -57,7 +57,7 @@ from plexpy import version +
+
+
+ + % endif % endfor diff --git a/data/interfaces/default/images/home-stat_most-concurrent.png b/data/interfaces/default/images/home-stat_most-concurrent.png new file mode 100644 index 0000000000000000000000000000000000000000..085dc93eb193971cd91cfca0c794b52db91c1260 GIT binary patch literal 4864 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 GhyM)~t94HR literal 0 HcmV?d00001 diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 384290f9..ee3643ff 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -111,6 +111,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents() + diff --git a/plexpy/config.py b/plexpy/config.py index 491f2081..664a69ec 100644 --- a/plexpy/config.py +++ b/plexpy/config.py @@ -98,7 +98,8 @@ _CONFIG_DEFINITIONS = { 'HOME_STATS_LENGTH': (int, 'General', 30), 'HOME_STATS_TYPE': (int, 'General', 0), 'HOME_STATS_COUNT': (int, 'General', 5), - 'HOME_STATS_CARDS': (str, 'General', 'watch_statistics, top_tv, popular_tv, top_movies, popular_movies, top_music, popular_music, top_users, top_platforms, last_watched'), + 'HOME_STATS_CARDS': (str, 'General', 'watch_statistics, top_tv, popular_tv, top_movies, popular_movies, ' \ + 'top_music, popular_music, last_watched, top_users, top_platforms, most_concurrent'), 'HTTPS_CERT': (str, 'General', ''), 'HTTPS_KEY': (str, 'General', ''), 'HTTP_HOST': (str, 'General', '0.0.0.0'), diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index 8d797fae..d1b656e5 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -589,6 +589,38 @@ class DataFactory(object): home_stats.append({'stat_id': stat, 'rows': last_watched}) + elif stat == 'most_concurrent': + try: + query = 'SELECT started, stopped ' \ + 'FROM session_history ' + result = monitor_db.select(query) + except: + logger.warn("Unable to execute database query for get_home_stats: most_concurrent.") + return None + + times = {} + for item in result: + times.update({str(item['stopped']) + 'A': -1, str(item['started']) + 'B': 1}) + + count = 0 + last_start = 0 + most_concurrent = {'count': count} + + for key in sorted(times): + if times[key] == 1: + count += times[key] + if count >= most_concurrent['count']: + last_start = key + else: + if count >= most_concurrent['count']: + most_concurrent = {'count': count, + 'started': last_start[:-1], + 'stopped': key[:-1]} + count += times[key] + + home_stats.append({'stat_id': stat, + 'rows': [most_concurrent]}) + return home_stats def get_stream_details(self, row_id=None): From cb7ba7fddeedd4a8ec207027b2bbe1d8b4c3bde5 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sat, 12 Dec 2015 14:36:57 -0800 Subject: [PATCH 09/25] Clean up if statement in current activity header --- data/interfaces/default/current_activity_header.html | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/data/interfaces/default/current_activity_header.html b/data/interfaces/default/current_activity_header.html index 0727e558..04d8f65b 100644 --- a/data/interfaces/default/current_activity_header.html +++ b/data/interfaces/default/current_activity_header.html @@ -17,12 +17,10 @@ DOCUMENTATION :: END % if data != None: % if data == '0':

Activity

+ % elif data == '1': +

Activity ${data} stream

% else: - % if data == '1': -

Activity ${data} stream

- % else: -

Activity ${data} streams

- % endif +

Activity ${data} streams

% endif % else:

Activity

From 53876e8f0dcc32f813d35bd89b7c40bf32846c93 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sat, 12 Dec 2015 16:21:54 -0800 Subject: [PATCH 10/25] Add time range to most concurrent stat * Reorder cards --- data/interfaces/default/settings.html | 2 +- plexpy/datafactory.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index ee3643ff..e5e1cca0 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -109,10 +109,10 @@ available_notification_agents = sorted(notifiers.available_notification_agents() + - diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index d1b656e5..6dcdef26 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -592,7 +592,9 @@ class DataFactory(object): elif stat == 'most_concurrent': try: query = 'SELECT started, stopped ' \ - 'FROM session_history ' + 'FROM session_history ' \ + 'WHERE datetime(stopped, "unixepoch", "localtime") ' \ + '>= datetime("now", "-%s days", "localtime") ' % time_range result = monitor_db.select(query) except: logger.warn("Unable to execute database query for get_home_stats: most_concurrent.") From 66bb922012bc5597aa4904f9fb5828834fdc9b98 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sat, 12 Dec 2015 17:31:52 -0800 Subject: [PATCH 11/25] Add stream details to notification options * Also beautify modal --- data/interfaces/default/css/plexpy.css | 25 ++ data/interfaces/default/settings.html | 324 ++++++++++++++++--------- plexpy/notification_handler.py | 74 +++++- 3 files changed, 296 insertions(+), 127 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index cfbf5933..ef06c9dc 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -2520,4 +2520,29 @@ table[id^='history_child'] thead th { -ms-transition: background 0.3s; -o-transition: background 0.3s; transition: background 0.3s; +} + +.notification-params { + margin-top: 10px; + background-color: #282828; +} +.notification-params th { + padding-left: 10px; + height: 30px; +} +.notification-params td { + height: 25px; +} +.notification-params td:first-child { + padding-left: 10px; + width: 200px; +} +.notification-params td:not(:first-child) { + padding-right: 10px; +} +.notification-params tr:nth-child(odd) td { + background-color: rgba(255,255,255,0.035); +} +.notification-params tr:nth-child(even) td { + background-color: rgba(255,255,255,0.010); } \ No newline at end of file diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index e5e1cca0..b70f78b7 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -930,155 +930,248 @@ available_notification_agents = sorted(notifiers.available_notification_agents() diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index b70f78b7..86819976 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -716,6 +716,40 @@ available_notification_agents = sorted(notifiers.available_notification_agents() +
  • + + +
  • +
  • + + +
  • diff --git a/plexpy/activity_pinger.py b/plexpy/activity_pinger.py index 6cf12443..ee0479d3 100644 --- a/plexpy/activity_pinger.py +++ b/plexpy/activity_pinger.py @@ -36,6 +36,11 @@ def check_active_sessions(ws_request=False): global int_ping_count if session_list: + if int_ping_count >= 3: + logger.info(u"PlexPy Monitor :: The Plex Media Server is back up.") + # Fire off notifications + threading.Thread(target=notification_handler.notify_timeline, + kwargs=dict(notify_action='intup')).start() int_ping_count = 0 media_container = session_list['sessions'] @@ -248,7 +253,7 @@ def check_server_response(): server_response = pms_connect.get_server_response() global ext_ping_count - + # Check for remote access if server_response: @@ -267,6 +272,11 @@ def check_server_response(): % str(ext_ping_count)) # Reset external ping counter else: + if ext_ping_count >= 3: + logger.info(u"PlexPy Monitor :: Plex remote access is back up.") + # Fire off notifications + threading.Thread(target=notification_handler.notify_timeline, + kwargs=dict(notify_action='extup')).start() ext_ping_count = 0 if ext_ping_count == 3: diff --git a/plexpy/config.py b/plexpy/config.py index 664a69ec..5ae5a68f 100644 --- a/plexpy/config.py +++ b/plexpy/config.py @@ -48,6 +48,8 @@ _CONFIG_DEFINITIONS = { 'BOXCAR_ON_CREATED': (int, 'Boxcar', 0), 'BOXCAR_ON_EXTDOWN': (int, 'Boxcar', 0), 'BOXCAR_ON_INTDOWN': (int, 'Boxcar', 0), + 'BOXCAR_ON_EXTUP': (int, 'Boxcar', 0), + 'BOXCAR_ON_INTUP': (int, 'Boxcar', 0), 'BUFFER_THRESHOLD': (int, 'Monitoring', 3), 'BUFFER_WAIT': (int, 'Monitoring', 900), 'CACHE_DIR': (str, 'General', ''), @@ -75,6 +77,8 @@ _CONFIG_DEFINITIONS = { 'EMAIL_ON_CREATED': (int, 'Email', 0), 'EMAIL_ON_EXTDOWN': (int, 'Email', 0), 'EMAIL_ON_INTDOWN': (int, 'Email', 0), + 'EMAIL_ON_EXTUP': (int, 'Email', 0), + 'EMAIL_ON_INTUP': (int, 'Email', 0), 'ENABLE_HTTPS': (int, 'General', 0), 'FIRST_RUN_COMPLETE': (int, 'General', 0), 'FREEZE_DB': (int, 'General', 0), @@ -94,6 +98,8 @@ _CONFIG_DEFINITIONS = { 'GROWL_ON_CREATED': (int, 'Growl', 0), 'GROWL_ON_EXTDOWN': (int, 'Growl', 0), 'GROWL_ON_INTDOWN': (int, 'Growl', 0), + 'GROWL_ON_EXTUP': (int, 'Growl', 0), + 'GROWL_ON_INTUP': (int, 'Growl', 0), 'HOME_LIBRARY_CARDS': (str, 'General', 'library_statistics_first'), 'HOME_STATS_LENGTH': (int, 'General', 30), 'HOME_STATS_TYPE': (int, 'General', 0), @@ -122,6 +128,8 @@ _CONFIG_DEFINITIONS = { 'IFTTT_ON_CREATED': (int, 'IFTTT', 0), 'IFTTT_ON_EXTDOWN': (int, 'IFTTT', 0), 'IFTTT_ON_INTDOWN': (int, 'IFTTT', 0), + 'IFTTT_ON_EXTUP': (int, 'IFTTT', 0), + 'IFTTT_ON_INTUP': (int, 'IFTTT', 0), 'JOURNAL_MODE': (str, 'Advanced', 'wal'), 'LAUNCH_BROWSER': (int, 'General', 1), 'LOG_DIR': (str, 'General', ''), @@ -151,6 +159,8 @@ _CONFIG_DEFINITIONS = { 'NMA_ON_CREATED': (int, 'NMA', 0), 'NMA_ON_EXTDOWN': (int, 'NMA', 0), 'NMA_ON_INTDOWN': (int, 'NMA', 0), + 'NMA_ON_EXTUP': (int, 'NMA', 0), + 'NMA_ON_INTUP': (int, 'NMA', 0), 'NOTIFY_CONSECUTIVE': (int, 'Monitoring', 1), 'NOTIFY_RECENTLY_ADDED': (int, 'Monitoring', 0), 'NOTIFY_RECENTLY_ADDED_GRANDPARENT': (int, 'Monitoring', 0), @@ -174,6 +184,10 @@ _CONFIG_DEFINITIONS = { 'NOTIFY_ON_EXTDOWN_BODY_TEXT': (unicode, 'Monitoring', 'The Plex Media Server remote access is down.'), 'NOTIFY_ON_INTDOWN_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'), 'NOTIFY_ON_INTDOWN_BODY_TEXT': (unicode, 'Monitoring', 'The Plex Media Server is down.'), + 'NOTIFY_ON_EXTUP_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'), + 'NOTIFY_ON_EXTUP_BODY_TEXT': (unicode, 'Monitoring', 'The Plex Media Server remote access is back up.'), + 'NOTIFY_ON_INTUP_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'), + 'NOTIFY_ON_INTUP_BODY_TEXT': (unicode, 'Monitoring', 'The Plex Media Server is back up.'), 'OSX_NOTIFY_APP': (str, 'OSX_Notify', '/Applications/PlexPy'), 'OSX_NOTIFY_ENABLED': (int, 'OSX_Notify', 0), 'OSX_NOTIFY_ON_PLAY': (int, 'OSX_Notify', 0), @@ -185,6 +199,8 @@ _CONFIG_DEFINITIONS = { 'OSX_NOTIFY_ON_CREATED': (int, 'OSX_Notify', 0), 'OSX_NOTIFY_ON_EXTDOWN': (int, 'OSX_Notify', 0), 'OSX_NOTIFY_ON_INTDOWN': (int, 'OSX_Notify', 0), + 'OSX_NOTIFY_ON_EXTUP': (int, 'OSX_Notify', 0), + 'OSX_NOTIFY_ON_INTUP': (int, 'OSX_Notify', 0), 'PLEX_CLIENT_HOST': (str, 'Plex', ''), 'PLEX_ENABLED': (int, 'Plex', 0), 'PLEX_PASSWORD': (str, 'Plex', ''), @@ -198,6 +214,8 @@ _CONFIG_DEFINITIONS = { 'PLEX_ON_CREATED': (int, 'Plex', 0), 'PLEX_ON_EXTDOWN': (int, 'Plex', 0), 'PLEX_ON_INTDOWN': (int, 'Plex', 0), + 'PLEX_ON_EXTUP': (int, 'Plex', 0), + 'PLEX_ON_INTUP': (int, 'Plex', 0), 'PROWL_ENABLED': (int, 'Prowl', 0), 'PROWL_KEYS': (str, 'Prowl', ''), 'PROWL_PRIORITY': (int, 'Prowl', 0), @@ -210,6 +228,8 @@ _CONFIG_DEFINITIONS = { 'PROWL_ON_CREATED': (int, 'Prowl', 0), 'PROWL_ON_EXTDOWN': (int, 'Prowl', 0), 'PROWL_ON_INTDOWN': (int, 'Prowl', 0), + 'PROWL_ON_EXTUP': (int, 'Prowl', 0), + 'PROWL_ON_INTUP': (int, 'Prowl', 0), 'PUSHALOT_APIKEY': (str, 'Pushalot', ''), 'PUSHALOT_ENABLED': (int, 'Pushalot', 0), 'PUSHALOT_ON_PLAY': (int, 'Pushalot', 0), @@ -221,6 +241,8 @@ _CONFIG_DEFINITIONS = { 'PUSHALOT_ON_CREATED': (int, 'Pushalot', 0), 'PUSHALOT_ON_EXTDOWN': (int, 'Pushalot', 0), 'PUSHALOT_ON_INTDOWN': (int, 'Pushalot', 0), + 'PUSHALOT_ON_EXTUP': (int, 'Pushalot', 0), + 'PUSHALOT_ON_INTUP': (int, 'Pushalot', 0), 'PUSHBULLET_APIKEY': (str, 'PushBullet', ''), 'PUSHBULLET_DEVICEID': (str, 'PushBullet', ''), 'PUSHBULLET_CHANNEL_TAG': (str, 'PushBullet', ''), @@ -234,6 +256,8 @@ _CONFIG_DEFINITIONS = { 'PUSHBULLET_ON_CREATED': (int, 'PushBullet', 0), 'PUSHBULLET_ON_EXTDOWN': (int, 'PushBullet', 0), 'PUSHBULLET_ON_INTDOWN': (int, 'PushBullet', 0), + 'PUSHBULLET_ON_EXTUP': (int, 'PushBullet', 0), + 'PUSHBULLET_ON_INTUP': (int, 'PushBullet', 0), 'PUSHOVER_APITOKEN': (str, 'Pushover', ''), 'PUSHOVER_ENABLED': (int, 'Pushover', 0), 'PUSHOVER_KEYS': (str, 'Pushover', ''), @@ -248,6 +272,8 @@ _CONFIG_DEFINITIONS = { 'PUSHOVER_ON_CREATED': (int, 'Pushover', 0), 'PUSHOVER_ON_EXTDOWN': (int, 'Pushover', 0), 'PUSHOVER_ON_INTDOWN': (int, 'Pushover', 0), + 'PUSHOVER_ON_EXTUP': (int, 'Pushover', 0), + 'PUSHOVER_ON_INTUP': (int, 'Pushover', 0), 'REFRESH_USERS_INTERVAL': (int, 'Monitoring', 12), 'REFRESH_USERS_ON_STARTUP': (int, 'Monitoring', 1), 'TELEGRAM_BOT_TOKEN': (str, 'Telegram', ''), @@ -262,6 +288,8 @@ _CONFIG_DEFINITIONS = { 'TELEGRAM_ON_CREATED': (int, 'Telegram', 0), 'TELEGRAM_ON_EXTDOWN': (int, 'Telegram', 0), 'TELEGRAM_ON_INTDOWN': (int, 'Telegram', 0), + 'TELEGRAM_ON_EXTUP': (int, 'Telegram', 0), + 'TELEGRAM_ON_INTUP': (int, 'Telegram', 0), 'TV_LOGGING_ENABLE': (int, 'Monitoring', 1), 'TV_NOTIFY_ENABLE': (int, 'Monitoring', 0), 'TV_NOTIFY_ON_START': (int, 'Monitoring', 1), @@ -280,6 +308,8 @@ _CONFIG_DEFINITIONS = { 'TWITTER_ON_CREATED': (int, 'Twitter', 0), 'TWITTER_ON_EXTDOWN': (int, 'Twitter', 0), 'TWITTER_ON_INTDOWN': (int, 'Twitter', 0), + 'TWITTER_ON_EXTUP': (int, 'Twitter', 0), + 'TWITTER_ON_INTUP': (int, 'Twitter', 0), 'UPDATE_DB_INTERVAL': (int, 'General', 24), 'VERIFY_SSL_CERT': (bool_int, 'Advanced', 1), 'VIDEO_LOGGING_ENABLE': (int, 'Monitoring', 1), @@ -295,7 +325,9 @@ _CONFIG_DEFINITIONS = { 'XBMC_ON_WATCHED': (int, 'XBMC', 0), 'XBMC_ON_CREATED': (int, 'XBMC', 0), 'XBMC_ON_EXTDOWN': (int, 'XBMC', 0), - 'XBMC_ON_INTDOWN': (int, 'XBMC', 0) + 'XBMC_ON_INTDOWN': (int, 'XBMC', 0), + 'XBMC_ON_EXTUP': (int, 'XBMC', 0), + 'XBMC_ON_INTUP': (int, 'XBMC', 0) } # pylint:disable=R0902 # it might be nice to refactor for fewer instance variables diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index b5554252..3bf22332 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -199,6 +199,18 @@ def notify_timeline(timeline_data=None, notify_action=None): notifiers.send_notification(config_id=agent['id'], subject=notify_strings[0], body=notify_strings[1]) + if agent['on_extup'] and notify_action == 'extup': + # Build and send notification + notify_strings = build_server_notify_text(state=notify_action) + notifiers.send_notification(config_id=agent['id'], + subject=notify_strings[0], + body=notify_strings[1]) + if agent['on_intup'] and notify_action == 'intup': + # Build and send notification + notify_strings = build_server_notify_text(state=notify_action) + notifiers.send_notification(config_id=agent['id'], + subject=notify_strings[0], + body=notify_strings[1]) else: logger.debug(u"PlexPy Notifier :: Notify timeline called but incomplete data received.") @@ -698,6 +710,10 @@ def build_server_notify_text(state=None): on_extdown_body = plexpy.CONFIG.NOTIFY_ON_EXTDOWN_BODY_TEXT on_intdown_subject = plexpy.CONFIG.NOTIFY_ON_INTDOWN_SUBJECT_TEXT on_intdown_body = plexpy.CONFIG.NOTIFY_ON_INTDOWN_BODY_TEXT + on_extup_subject = plexpy.CONFIG.NOTIFY_ON_EXTUP_SUBJECT_TEXT + on_extup_body = plexpy.CONFIG.NOTIFY_ON_EXTUP_BODY_TEXT + on_intup_subject = plexpy.CONFIG.NOTIFY_ON_INTUP_SUBJECT_TEXT + on_intup_body = plexpy.CONFIG.NOTIFY_ON_INTUP_BODY_TEXT available_params = {'server_name': server_name, 'server_uptime': server_uptime} @@ -746,6 +762,50 @@ def build_server_notify_text(state=None): except: logger.error(u"PlexPy Notifier :: Unable to parse custom notification body. Using fallback.") + return [subject_text, body_text] + else: + return [subject_text, body_text] + if state == 'extup': + # Default body text + body_text = 'The Plex Media Server remote access is back up.' + + if on_extup_subject and on_extup_body: + try: + subject_text = unicode(on_extup_subject).format(**available_params) + except LookupError, e: + logger.error(u"PlexPy Notifier :: Unable to parse field %s in notification subject. Using fallback." % e) + except: + logger.error(u"PlexPy Notifier :: Unable to parse custom notification subject. Using fallback.") + + try: + body_text = unicode(on_extup_body).format(**available_params) + except LookupError, e: + logger.error(u"PlexPy Notifier :: Unable to parse field %s in notification body. Using fallback." % e) + except: + logger.error(u"PlexPy Notifier :: Unable to parse custom notification body. Using fallback.") + + return [subject_text, body_text] + else: + return [subject_text, body_text] + elif state == 'intup': + # Default body text + body_text = 'The Plex Media Server is back up.' + + if on_intup_subject and on_intup_body: + try: + subject_text = unicode(on_intup_subject).format(**available_params) + except LookupError, e: + logger.error(u"PlexPy Notifier :: Unable to parse field %s in notification subject. Using fallback." % e) + except: + logger.error(u"PlexPy Notifier :: Unable to parse custom notification subject. Using fallback.") + + try: + body_text = unicode(on_intup_body).format(**available_params) + except LookupError, e: + logger.error(u"PlexPy Notifier :: Unable to parse field %s in notification body. Using fallback." % e) + except: + logger.error(u"PlexPy Notifier :: Unable to parse custom notification body. Using fallback.") + return [subject_text, body_text] else: return [subject_text, body_text] diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index 9c616af3..aa126df4 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -68,7 +68,9 @@ def available_notification_agents(): 'on_watched': plexpy.CONFIG.GROWL_ON_WATCHED, 'on_created': plexpy.CONFIG.GROWL_ON_CREATED, 'on_extdown': plexpy.CONFIG.GROWL_ON_EXTDOWN, - 'on_intdown': plexpy.CONFIG.GROWL_ON_INTDOWN + 'on_intdown': plexpy.CONFIG.GROWL_ON_INTDOWN, + 'on_extup': plexpy.CONFIG.GROWL_ON_EXTUP, + 'on_intup': plexpy.CONFIG.GROWL_ON_INTUP }, {'name': 'Prowl', 'id': AGENT_IDS['Prowl'], @@ -83,7 +85,9 @@ def available_notification_agents(): 'on_watched': plexpy.CONFIG.PROWL_ON_WATCHED, 'on_created': plexpy.CONFIG.PROWL_ON_CREATED, 'on_extdown': plexpy.CONFIG.PROWL_ON_EXTDOWN, - 'on_intdown': plexpy.CONFIG.PROWL_ON_INTDOWN + 'on_intdown': plexpy.CONFIG.PROWL_ON_INTDOWN, + 'on_extup': plexpy.CONFIG.PROWL_ON_EXTUP, + 'on_intup': plexpy.CONFIG.PROWL_ON_INTUP }, {'name': 'XBMC', 'id': AGENT_IDS['XBMC'], @@ -98,7 +102,9 @@ def available_notification_agents(): 'on_watched': plexpy.CONFIG.XBMC_ON_WATCHED, 'on_created': plexpy.CONFIG.XBMC_ON_CREATED, 'on_extdown': plexpy.CONFIG.XBMC_ON_EXTDOWN, - 'on_intdown': plexpy.CONFIG.XBMC_ON_INTDOWN + 'on_intdown': plexpy.CONFIG.XBMC_ON_INTDOWN, + 'on_extup': plexpy.CONFIG.XBMC_ON_EXTUP, + 'on_intup': plexpy.CONFIG.XBMC_ON_INTUP }, {'name': 'Plex', 'id': AGENT_IDS['Plex'], @@ -113,7 +119,9 @@ def available_notification_agents(): 'on_watched': plexpy.CONFIG.PLEX_ON_WATCHED, 'on_created': plexpy.CONFIG.PLEX_ON_CREATED, 'on_extdown': plexpy.CONFIG.PLEX_ON_EXTDOWN, - 'on_intdown': plexpy.CONFIG.PLEX_ON_INTDOWN + 'on_intdown': plexpy.CONFIG.PLEX_ON_INTDOWN, + 'on_extup': plexpy.CONFIG.PLEX_ON_EXTUP, + 'on_intup': plexpy.CONFIG.PLEX_ON_INTUP }, {'name': 'NotifyMyAndroid', 'id': AGENT_IDS['NMA'], @@ -128,7 +136,9 @@ def available_notification_agents(): 'on_watched': plexpy.CONFIG.NMA_ON_WATCHED, 'on_created': plexpy.CONFIG.NMA_ON_CREATED, 'on_extdown': plexpy.CONFIG.NMA_ON_EXTDOWN, - 'on_intdown': plexpy.CONFIG.NMA_ON_INTDOWN + 'on_intdown': plexpy.CONFIG.NMA_ON_INTDOWN, + 'on_extup': plexpy.CONFIG.NMA_ON_EXTUP, + 'on_intup': plexpy.CONFIG.NMA_ON_INTUP }, {'name': 'Pushalot', 'id': AGENT_IDS['Pushalot'], @@ -143,7 +153,9 @@ def available_notification_agents(): 'on_watched': plexpy.CONFIG.PUSHALOT_ON_WATCHED, 'on_created': plexpy.CONFIG.PUSHALOT_ON_CREATED, 'on_extdown': plexpy.CONFIG.PUSHALOT_ON_EXTDOWN, - 'on_intdown': plexpy.CONFIG.PUSHALOT_ON_INTDOWN + 'on_intdown': plexpy.CONFIG.PUSHALOT_ON_INTDOWN, + 'on_extup': plexpy.CONFIG.PUSHALOT_ON_EXTUP, + 'on_intup': plexpy.CONFIG.PUSHALOT_ON_INTUP }, {'name': 'Pushbullet', 'id': AGENT_IDS['Pushbullet'], @@ -158,7 +170,9 @@ def available_notification_agents(): 'on_watched': plexpy.CONFIG.PUSHBULLET_ON_WATCHED, 'on_created': plexpy.CONFIG.PUSHBULLET_ON_CREATED, 'on_extdown': plexpy.CONFIG.PUSHBULLET_ON_EXTDOWN, - 'on_intdown': plexpy.CONFIG.PUSHBULLET_ON_INTDOWN + 'on_intdown': plexpy.CONFIG.PUSHBULLET_ON_INTDOWN, + 'on_extup': plexpy.CONFIG.PUSHBULLET_ON_EXTUP, + 'on_intup': plexpy.CONFIG.PUSHBULLET_ON_INTUP }, {'name': 'Pushover', 'id': AGENT_IDS['Pushover'], @@ -173,7 +187,9 @@ def available_notification_agents(): 'on_watched': plexpy.CONFIG.PUSHOVER_ON_WATCHED, 'on_created': plexpy.CONFIG.PUSHOVER_ON_CREATED, 'on_extdown': plexpy.CONFIG.PUSHOVER_ON_EXTDOWN, - 'on_intdown': plexpy.CONFIG.PUSHOVER_ON_INTDOWN + 'on_intdown': plexpy.CONFIG.PUSHOVER_ON_INTDOWN, + 'on_extup': plexpy.CONFIG.PUSHOVER_ON_EXTUP, + 'on_intup': plexpy.CONFIG.PUSHOVER_ON_INTUP }, {'name': 'Boxcar2', 'id': AGENT_IDS['Boxcar2'], @@ -188,7 +204,9 @@ def available_notification_agents(): 'on_watched': plexpy.CONFIG.BOXCAR_ON_WATCHED, 'on_created': plexpy.CONFIG.BOXCAR_ON_CREATED, 'on_extdown': plexpy.CONFIG.BOXCAR_ON_EXTDOWN, - 'on_intdown': plexpy.CONFIG.BOXCAR_ON_INTDOWN + 'on_intdown': plexpy.CONFIG.BOXCAR_ON_INTDOWN, + 'on_extup': plexpy.CONFIG.BOXCAR_ON_EXTUP, + 'on_intup': plexpy.CONFIG.BOXCAR_ON_INTUP }, {'name': 'E-mail', 'id': AGENT_IDS['Email'], @@ -203,7 +221,9 @@ def available_notification_agents(): 'on_watched': plexpy.CONFIG.EMAIL_ON_WATCHED, 'on_created': plexpy.CONFIG.EMAIL_ON_CREATED, 'on_extdown': plexpy.CONFIG.EMAIL_ON_EXTDOWN, - 'on_intdown': plexpy.CONFIG.EMAIL_ON_INTDOWN + 'on_intdown': plexpy.CONFIG.EMAIL_ON_INTDOWN, + 'on_extup': plexpy.CONFIG.EMAIL_ON_EXTUP, + 'on_intup': plexpy.CONFIG.EMAIL_ON_INTUP }, {'name': 'Twitter', 'id': AGENT_IDS['Twitter'], @@ -218,7 +238,9 @@ def available_notification_agents(): 'on_watched': plexpy.CONFIG.TWITTER_ON_WATCHED, 'on_created': plexpy.CONFIG.TWITTER_ON_CREATED, 'on_extdown': plexpy.CONFIG.TWITTER_ON_EXTDOWN, - 'on_intdown': plexpy.CONFIG.TWITTER_ON_INTDOWN + 'on_intdown': plexpy.CONFIG.TWITTER_ON_INTDOWN, + 'on_extup': plexpy.CONFIG.TWITTER_ON_EXTUP, + 'on_intup': plexpy.CONFIG.TWITTER_ON_INTUP }, {'name': 'IFTTT', 'id': AGENT_IDS['IFTTT'], @@ -233,7 +255,9 @@ def available_notification_agents(): 'on_watched': plexpy.CONFIG.IFTTT_ON_WATCHED, 'on_created': plexpy.CONFIG.IFTTT_ON_CREATED, 'on_extdown': plexpy.CONFIG.IFTTT_ON_EXTDOWN, - 'on_intdown': plexpy.CONFIG.IFTTT_ON_INTDOWN + 'on_intdown': plexpy.CONFIG.IFTTT_ON_INTDOWN, + 'on_extup': plexpy.CONFIG.IFTTT_ON_EXTUP, + 'on_intup': plexpy.CONFIG.IFTTT_ON_INTUP }, {'name': 'Telegram', 'id': AGENT_IDS['Telegram'], @@ -248,7 +272,9 @@ def available_notification_agents(): 'on_watched': plexpy.CONFIG.TELEGRAM_ON_WATCHED, 'on_created': plexpy.CONFIG.TELEGRAM_ON_CREATED, 'on_extdown': plexpy.CONFIG.TELEGRAM_ON_EXTDOWN, - 'on_intdown': plexpy.CONFIG.TELEGRAM_ON_INTDOWN + 'on_intdown': plexpy.CONFIG.TELEGRAM_ON_INTDOWN, + 'on_extup': plexpy.CONFIG.TELEGRAM_ON_EXTUP, + 'on_intup': plexpy.CONFIG.TELEGRAM_ON_INTUP } ] @@ -268,7 +294,9 @@ def available_notification_agents(): 'on_watched': plexpy.CONFIG.OSX_NOTIFY_ON_WATCHED, 'on_created': plexpy.CONFIG.OSX_NOTIFY_ON_CREATED, 'on_extdown': plexpy.CONFIG.OSX_NOTIFY_ON_EXTDOWN, - 'on_intdown': plexpy.CONFIG.OSX_NOTIFY_ON_INTDOWN + 'on_intdown': plexpy.CONFIG.OSX_NOTIFY_ON_INTDOWN, + 'on_extup': plexpy.CONFIG.OSX_NOTIFY_ON_EXTUP, + 'on_intup': plexpy.CONFIG.OSX_NOTIFY_ON_INTUP }) return agents diff --git a/plexpy/webserve.py b/plexpy/webserve.py index fb260fe6..ff52d69e 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -467,6 +467,10 @@ class WebInterface(object): "notify_on_extdown_body_text": plexpy.CONFIG.NOTIFY_ON_EXTDOWN_BODY_TEXT, "notify_on_intdown_subject_text": plexpy.CONFIG.NOTIFY_ON_INTDOWN_SUBJECT_TEXT, "notify_on_intdown_body_text": plexpy.CONFIG.NOTIFY_ON_INTDOWN_BODY_TEXT, + "notify_on_extup_subject_text": plexpy.CONFIG.NOTIFY_ON_EXTUP_SUBJECT_TEXT, + "notify_on_extup_body_text": plexpy.CONFIG.NOTIFY_ON_EXTUP_BODY_TEXT, + "notify_on_intup_subject_text": plexpy.CONFIG.NOTIFY_ON_INTUP_SUBJECT_TEXT, + "notify_on_intup_body_text": plexpy.CONFIG.NOTIFY_ON_INTUP_BODY_TEXT, "home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH, "home_stats_type": checked(plexpy.CONFIG.HOME_STATS_TYPE), "home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT, From f9f65eae5330d02ffe854bc49ec5a5b3f3c4029a Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sun, 13 Dec 2015 09:36:54 -0800 Subject: [PATCH 14/25] Add duration to history table footer --- .../default/js/tables/history_table.js | 5 ++- plexpy/datafactory.py | 36 +++++++++++++++++-- plexpy/helpers.py | 20 ++++++----- plexpy/webserve.py | 2 +- 4 files changed, 50 insertions(+), 13 deletions(-) diff --git a/data/interfaces/default/js/tables/history_table.js b/data/interfaces/default/js/tables/history_table.js index 1cafd218..516ed0bc 100644 --- a/data/interfaces/default/js/tables/history_table.js +++ b/data/interfaces/default/js/tables/history_table.js @@ -18,7 +18,7 @@ history_table_options = { "lengthMenu":"Show _MENU_ entries per page", "info":"Showing _START_ to _END_ of _TOTAL_ history items", "infoEmpty":"Showing 0 to 0 of 0 entries", - "infoFiltered":"(filtered from _MAX_ total entries)", + "infoFiltered":"", "emptyTable": "No data in table" }, "pagingType": "bootstrap", @@ -265,6 +265,9 @@ history_table_options = { createChildTable(this, rowData) } }); + + $("#history_table_info").append(''); }, "preDrawCallback": function(settings) { var msg = " Fetching rows..."; diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index 6dcdef26..4d7a4fdb 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -45,7 +45,7 @@ class DataFactory(object): 'platform', 'player', 'ip_address', - 'session_history_metadata.media_type', + 'session_history.media_type', 'session_history_metadata.rating_key', 'session_history_metadata.parent_rating_key', 'session_history_metadata.grandparent_rating_key', @@ -80,7 +80,7 @@ class DataFactory(object): ['session_history.id', 'session_history_media_info.id']], kwargs=kwargs) except: - logger.warn("Unable to execute database query.") + logger.warn("Unable to execute database query for get_history.") return {'recordsFiltered': 0, 'recordsTotal': 0, 'draw': 0, @@ -89,8 +89,13 @@ class DataFactory(object): history = query['result'] + filter_duration = 0 + total_duration = self.get_total_duration(custom_where=custom_where) + rows = [] for item in history: + filter_duration += int(item['duration']) + if item["media_type"] == 'episode' and item["parent_thumb"]: thumb = item["parent_thumb"] elif item["media_type"] == 'episode': @@ -144,7 +149,9 @@ class DataFactory(object): dict = {'recordsFiltered': query['filteredCount'], 'recordsTotal': query['totalCount'], 'data': rows, - 'draw': query['draw'] + 'draw': query['draw'], + 'filter_duration': helpers.human_duration(filter_duration, sig='dhm'), + 'total_duration': helpers.human_duration(total_duration, sig='dhm') } return dict @@ -1083,3 +1090,26 @@ class DataFactory(object): ip_address = item['ip_address'] return ip_address + + def get_total_duration(self, custom_where=None): + monitor_db = database.MonitorDatabase() + + # Split up custom wheres + if custom_where: + where = 'WHERE ' + ' AND '.join([w[0] + ' = "' + w[1] + '"' for w in custom_where]) + else: + where = '' + + try: + query = 'SELECT 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 total_duration ' \ + 'FROM session_history %s ' % where + result = monitor_db.select(query) + except: + logger.warn("Unable to execute database query for get_total_duration.") + return None + + for item in result: + total_duration = item['total_duration'] + + return total_duration \ No newline at end of file diff --git a/plexpy/helpers.py b/plexpy/helpers.py index b4f86f49..6319f423 100644 --- a/plexpy/helpers.py +++ b/plexpy/helpers.py @@ -146,7 +146,7 @@ def now(): now = datetime.datetime.now() return now.strftime("%Y-%m-%d %H:%M:%S") -def human_duration(s): +def human_duration(s, sig='dhms'): hd = '' @@ -157,20 +157,24 @@ def human_duration(s): s = int(((s % 84600) % 3600) % 60) hd_list = [] - if d > 0: + if sig >= 'd' and d > 0: + d = d + 1 if sig == 'd' and h >= 12 else d hd_list.append(str(d) + ' days') - if h > 0: + + if sig >= 'dh' and h > 0: + h = h + 1 if sig == 'dh' and m >= 30 else h hd_list.append(str(h) + ' hrs') - if m > 0: + + if sig >= 'dhm' and m > 0: + m = m + 1 if sig == 'dhm' and s >= 30 else m hd_list.append(str(m) + ' mins') - if s > 0: + + if sig >= 'dhms' and s > 0: hd_list.append(str(s) + ' secs') hd = ' '.join(hd_list) - return hd - else: - return hd + return hd def get_age(date): diff --git a/plexpy/webserve.py b/plexpy/webserve.py index ff52d69e..2251b85e 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -617,7 +617,7 @@ class WebInterface(object): if 'media_type' in kwargs: media_type = kwargs.get('media_type', "") if media_type != 'all': - custom_where.append(['session_history_metadata.media_type', media_type]) + custom_where.append(['session_history.media_type', media_type]) data_factory = datafactory.DataFactory() history = data_factory.get_history(kwargs=kwargs, custom_where=custom_where, grouping=grouping, watched_percent=watched_percent) From c6cc2b88311744625510c61611943b3943021e03 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sun, 13 Dec 2015 11:35:33 -0800 Subject: [PATCH 15/25] Save graph type/days/tab to config file * Change input for graph days range --- data/interfaces/default/css/plexpy.css | 9 + data/interfaces/default/graphs.html | 260 ++++++++++++++----------- plexpy/config.py | 3 + plexpy/webserve.py | 17 ++ 4 files changed, 173 insertions(+), 116 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index ef06c9dc..5673d854 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -2545,4 +2545,13 @@ table[id^='history_child'] thead th { } .notification-params tr:nth-child(even) td { background-color: rgba(255,255,255,0.010); +} + +#days-selection label { + margin-bottom: 0; +} +#graph-days { + margin: 0; + width: 75px; + height: 34px; } \ No newline at end of file diff --git a/data/interfaces/default/graphs.html b/data/interfaces/default/graphs.html index 8f114b67..9143bb5a 100644 --- a/data/interfaces/default/graphs.html +++ b/data/interfaces/default/graphs.html @@ -13,110 +13,132 @@
    + % if config['graph_tab'] != 'tabs-2' and config['graph_tab'] != 'tabs-3':
    -
    -
    -

    Daily Play count Last 30 days

    -

    - The total play count or duration of tv, movies, and music played per day. Click a graph point to open up a list of items played for that specific date. -

    -
    -
    -
    Loading chart...
    -
    + % else: +
    + % endif +
    +
    +

    Daily Play count Last 30 days

    +

    + The total play count or duration of tv, movies, and music played per day. Click a graph point to open up a list of items played for that specific date. +

    +
    +
    +
    Loading chart...
    +
    +
    +
    +
    +
    +
    +
    +

    Play count by day of week Last 30 days

    +

    + The combined total of tv, movies, and music played per day of the week. +

    +
    +
    +
    + Loading chart... +
    +
    +
    +
    +
    +
    +

    Play count by hour of day Last 30 days

    +

    + The combined total of tv, movies, and music played per hour of the day. +

    +
    +
    +
    + Loading chart... +
    +
    +
    +
    +
    +
    +
    +
    +

    Play count by top 10 platforms Last 30 days

    +

    + The combined total of tv, movies, and music played by top 10 most active platforms. +

    +
    +
    +
    + Loading chart... +
    +
    +
    +
    +
    +
    +

    Play count by top 10 users Last 30 days

    +

    + The combined total of tv, movies, and music played by top 10 most active users. +

    +
    +
    +
    + Loading chart... +
    +
    +
    -
    -
    -

    Play count by day of week Last 30 days

    -

    - The combined total of tv, movies, and music played per day of the week. -

    -
    -
    -
    Loading chart... -
    -
    -
    -
    -
    -
    -

    Play count by hour of day Last 30 days

    -

    - The combined total of tv, movies, and music played per hour of the day. -

    -
    -
    -
    Loading chart... -
    -
    -
    -
    -
    -
    -
    -
    -

    Play count by top 10 platforms Last 30 days

    -

    - The combined total of tv, movies, and music played by top 10 most active platforms. -

    -
    -
    -
    Loading chart... -
    -
    -
    -
    -
    -
    -

    Play count by top 10 users Last 30 days

    -

    - The combined total of tv, movies, and music played by top 10 most active users. -

    -
    -
    -
    Loading chart... -
    -
    -
    -
    -
    -
    -
    + % if config['graph_tab'] == 'tabs-2': +
    + % else:
    + % endif

    Daily Stream type breakdown Last 30 days

    @@ -189,7 +211,11 @@
    + % if config['graph_tab'] == 'tabs-3': +
    + % else:
    + % endif

    Plays by Month Last 12 months

    @@ -263,36 +289,12 @@ % else:
    Nothing is currently being watched.

    From 447c50fd03341933b30d7f202f0973f7647a9515 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sat, 19 Dec 2015 20:40:30 -0800 Subject: [PATCH 23/25] Group watch statistics history --- data/interfaces/default/css/plexpy.css | 6 +- data/interfaces/default/settings.html | 4 +- plexpy/datafactory.py | 307 +++++++++++-------------- plexpy/webserve.py | 6 +- 4 files changed, 150 insertions(+), 173 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index fc1449a6..5ae38fe4 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -487,7 +487,8 @@ textarea.form-control:focus { .users-poster-face { overflow: hidden; float: left; - background-size: contain; + background-size: cover; + background-position: center; height: 40px; width: 40px; -webkit-border-radius: 50%; @@ -1537,7 +1538,8 @@ a:hover .item-children-poster { float: left; margin-top: 15px; margin-right: 15px; - background-size: contain; + background-size: cover; + background-position: center; height: 80px; width: 80px; -webkit-border-radius: 50%; diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 08d76466..2683860b 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -86,9 +86,9 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
    -

    Group successive play history by the same user as a single entry in tables.

    +

    Group successive play history by the same user as a single entry in the tables and watch statistics.

    diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index 4d7a4fdb..0e489684 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -37,11 +37,12 @@ class DataFactory(object): '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 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 users.username ELSE users.friendly_name END) as friendly_name', + '(CASE WHEN users.friendly_name IS NULL THEN users.username ELSE users.friendly_name END) \ + AS friendly_name', 'platform', 'player', 'ip_address', @@ -58,7 +59,8 @@ class DataFactory(object): 'session_history_metadata.parent_thumb', 'session_history_metadata.grandparent_thumb', '((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', + (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', 'COUNT(*) AS group_count', @@ -156,9 +158,10 @@ class DataFactory(object): return dict - def get_home_stats(self, time_range='30', stats_type=0, stats_count='5', stats_cards='', notify_watched_percent='85'): + def get_home_stats(self, grouping=0, time_range='30', stats_type=0, stats_count='5', stats_cards='', notify_watched_percent='85'): monitor_db = database.MonitorDatabase() + group_by = 'session_history.reference_id' if grouping else 'session_history.id' sort_type = 'total_plays' if stats_type == 0 else 'total_duration' home_stats = [] @@ -167,23 +170,20 @@ class DataFactory(object): if stat == 'top_tv': top_tv = [] try: - query = 'SELECT session_history_metadata.id, ' \ - 'session_history_metadata.grandparent_title, ' \ - 'COUNT(session_history_metadata.grandparent_title) as total_plays, ' \ - 'SUM(case when session_history.stopped > 0 ' \ - 'then (session_history.stopped - session_history.started) ' \ - ' - (case when session_history.paused_counter is NULL then 0 else session_history.paused_counter end) ' \ - 'else 0 end) as total_duration, ' \ - 'session_history_metadata.grandparent_rating_key, ' \ - 'MAX(session_history.started) as last_watch,' \ - 'session_history_metadata.grandparent_thumb ' \ - 'FROM session_history_metadata ' \ - 'JOIN session_history on session_history_metadata.id = session_history.id ' \ - 'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ - '>= datetime("now", "-%s days", "localtime") ' \ - 'AND session_history_metadata.media_type = "episode" ' \ - 'GROUP BY session_history_metadata.grandparent_title ' \ - 'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stats_count) + query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, ' \ + 'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, ' \ + 'SUM(CASE WHEN t.stopped > 0 THEN (t.stopped - t.started) ' \ + ' - (CASE WHEN t.paused_counter IS NULL THEN 0 ELSE t.paused_counter END) ELSE 0 END) ' \ + ' AS total_duration ' \ + 'FROM (SELECT * FROM session_history ' \ + ' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \ + ' WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ + ' >= datetime("now", "-%s days", "localtime") ' \ + ' AND session_history.media_type = "episode" ' \ + ' GROUP BY %s) AS t ' \ + 'GROUP BY t.grandparent_title ' \ + 'ORDER BY %s DESC ' \ + 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) result = monitor_db.select(query) except: logger.warn("Unable to execute database query for get_home_stats: top_tv.") @@ -213,25 +213,21 @@ class DataFactory(object): elif stat == 'popular_tv': popular_tv = [] try: - query = 'SELECT session_history_metadata.id, ' \ - 'session_history_metadata.grandparent_title, ' \ - 'COUNT(DISTINCT session_history.user_id) as users_watched, ' \ - 'session_history_metadata.grandparent_rating_key, ' \ - 'MAX(session_history.started) as last_watch, ' \ - 'COUNT(session_history.id) as total_plays, ' \ - 'SUM(case when session_history.stopped > 0 ' \ - 'then (session_history.stopped - session_history.started) ' \ - ' - (case when session_history.paused_counter is NULL then 0 else session_history.paused_counter end) ' \ - 'else 0 end) as total_duration, ' \ - 'session_history_metadata.grandparent_thumb ' \ - 'FROM session_history_metadata ' \ - 'JOIN session_history ON session_history_metadata.id = session_history.id ' \ - 'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ - '>= datetime("now", "-%s days", "localtime") ' \ - 'AND session_history_metadata.media_type = "episode" ' \ - 'GROUP BY session_history_metadata.grandparent_title ' \ + query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, ' \ + 'COUNT(DISTINCT t.user_id) AS users_watched, ' \ + 'MAX(t.started) AS last_watch, COUNT(t.id) as total_plays, ' \ + 'SUM(CASE WHEN t.stopped > 0 THEN (t.stopped - t.started) ' \ + ' - (CASE WHEN t.paused_counter IS NULL THEN 0 ELSE t.paused_counter END) ELSE 0 END) ' \ + ' AS total_duration ' \ + 'FROM (SELECT * FROM session_history ' \ + ' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \ + ' WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ + ' >= datetime("now", "-%s days", "localtime") ' \ + ' AND session_history.media_type = "episode" ' \ + ' GROUP BY %s) AS t ' \ + 'GROUP BY t.grandparent_title ' \ 'ORDER BY users_watched DESC, %s DESC ' \ - 'LIMIT %s' % (time_range, sort_type, stats_count) + 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) result = monitor_db.select(query) except: logger.warn("Unable to execute database query for get_home_stats: popular_tv.") @@ -259,23 +255,20 @@ class DataFactory(object): elif stat == 'top_movies': top_movies = [] try: - query = 'SELECT session_history_metadata.id, ' \ - 'session_history_metadata.full_title, ' \ - 'COUNT(session_history_metadata.full_title) as total_plays, ' \ - 'SUM(case when session_history.stopped > 0 ' \ - 'then (session_history.stopped - session_history.started) ' \ - ' - (case when session_history.paused_counter is NULL then 0 else session_history.paused_counter end) ' \ - 'else 0 end) as total_duration, ' \ - 'session_history_metadata.rating_key, ' \ - 'MAX(session_history.started) as last_watch,' \ - 'session_history_metadata.thumb ' \ - 'FROM session_history_metadata ' \ - 'JOIN session_history on session_history_metadata.id = session_history.id ' \ - 'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ - '>= datetime("now", "-%s days", "localtime") ' \ - 'AND session_history_metadata.media_type = "movie" ' \ - 'GROUP BY session_history_metadata.full_title ' \ - 'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stats_count) + query = 'SELECT t.id, t.full_title, t.rating_key, t.thumb, ' \ + 'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, ' \ + 'SUM(CASE WHEN t.stopped > 0 THEN (t.stopped - t.started) ' \ + ' - (CASE WHEN t.paused_counter IS NULL THEN 0 ELSE t.paused_counter END) ELSE 0 END) ' \ + ' AS total_duration ' \ + 'FROM (SELECT * FROM session_history ' \ + ' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \ + ' WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ + ' >= datetime("now", "-%s days", "localtime") ' \ + ' AND session_history.media_type = "movie" ' \ + ' GROUP BY %s) AS t ' \ + 'GROUP BY t.full_title ' \ + 'ORDER BY %s DESC ' \ + 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) result = monitor_db.select(query) except: logger.warn("Unable to execute database query for get_home_stats: top_movies.") @@ -297,7 +290,6 @@ class DataFactory(object): 'row_id': item['id'] } top_movies.append(row) - home_stats.append({'stat_id': stat, 'stat_type': sort_type, 'rows': top_movies}) @@ -305,25 +297,21 @@ class DataFactory(object): elif stat == 'popular_movies': popular_movies = [] try: - query = 'SELECT session_history_metadata.id, ' \ - 'session_history_metadata.full_title, ' \ - 'COUNT(DISTINCT session_history.user_id) as users_watched, ' \ - 'session_history_metadata.rating_key, ' \ - 'MAX(session_history.started) as last_watch, ' \ - 'COUNT(session_history.id) as total_plays, ' \ - 'SUM(case when session_history.stopped > 0 ' \ - 'then (session_history.stopped - session_history.started) ' \ - ' - (case when session_history.paused_counter is NULL then 0 else session_history.paused_counter end) ' \ - 'else 0 end) as total_duration, ' \ - 'session_history_metadata.thumb ' \ - 'FROM session_history_metadata ' \ - 'JOIN session_history ON session_history_metadata.id = session_history.id ' \ - 'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ - '>= datetime("now", "-%s days", "localtime") ' \ - 'AND session_history_metadata.media_type = "movie" ' \ - 'GROUP BY session_history_metadata.full_title ' \ + query = 'SELECT t.id, t.full_title, t.rating_key, t.thumb, ' \ + 'COUNT(DISTINCT t.user_id) AS users_watched, ' \ + 'MAX(t.started) AS last_watch, COUNT(t.id) as total_plays, ' \ + 'SUM(CASE WHEN t.stopped > 0 THEN (t.stopped - t.started) ' \ + ' - (CASE WHEN t.paused_counter IS NULL THEN 0 ELSE t.paused_counter END) ELSE 0 END) ' \ + ' AS total_duration ' \ + 'FROM (SELECT * FROM session_history ' \ + ' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \ + ' WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ + ' >= datetime("now", "-%s days", "localtime") ' \ + ' AND session_history.media_type = "movie" ' \ + ' GROUP BY %s) AS t ' \ + 'GROUP BY t.full_title ' \ 'ORDER BY users_watched DESC, %s DESC ' \ - 'LIMIT %s' % (time_range, sort_type, stats_count) + 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) result = monitor_db.select(query) except: logger.warn("Unable to execute database query for get_home_stats: popular_movies.") @@ -351,23 +339,20 @@ class DataFactory(object): elif stat == 'top_music': top_music = [] try: - query = 'SELECT session_history_metadata.id, ' \ - 'session_history_metadata.grandparent_title, ' \ - 'COUNT(session_history_metadata.grandparent_title) as total_plays, ' \ - 'SUM(case when session_history.stopped > 0 ' \ - 'then (session_history.stopped - session_history.started) ' \ - ' - (case when session_history.paused_counter is NULL then 0 else session_history.paused_counter end) ' \ - 'else 0 end) as total_duration, ' \ - 'session_history_metadata.grandparent_rating_key, ' \ - 'MAX(session_history.started) as last_watch,' \ - 'session_history_metadata.grandparent_thumb ' \ - 'FROM session_history_metadata ' \ - 'JOIN session_history on session_history_metadata.id = session_history.id ' \ - 'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ - '>= datetime("now", "-%s days", "localtime") ' \ - 'AND session_history_metadata.media_type = "track" ' \ - 'GROUP BY session_history_metadata.grandparent_title ' \ - 'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stats_count) + query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, ' \ + 'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, ' \ + 'SUM(CASE WHEN t.stopped > 0 THEN (t.stopped - t.started) ' \ + ' - (CASE WHEN t.paused_counter IS NULL THEN 0 ELSE t.paused_counter END) ELSE 0 END) ' \ + ' AS total_duration ' \ + 'FROM (SELECT * FROM session_history ' \ + ' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \ + ' WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ + ' >= datetime("now", "-%s days", "localtime") ' \ + ' AND session_history.media_type = "track" ' \ + ' GROUP BY %s) AS t ' \ + 'GROUP BY t.grandparent_title ' \ + 'ORDER BY %s DESC ' \ + 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) result = monitor_db.select(query) except: logger.warn("Unable to execute database query for get_home_stats: top_music.") @@ -397,25 +382,21 @@ class DataFactory(object): elif stat == 'popular_music': popular_music = [] try: - query = 'SELECT session_history_metadata.id, ' \ - 'session_history_metadata.grandparent_title, ' \ - 'COUNT(DISTINCT session_history.user_id) as users_watched, ' \ - 'session_history_metadata.grandparent_rating_key, ' \ - 'MAX(session_history.started) as last_watch, ' \ - 'COUNT(session_history.id) as total_plays, ' \ - 'SUM(case when session_history.stopped > 0 ' \ - 'then (session_history.stopped - session_history.started) ' \ - ' - (case when session_history.paused_counter is NULL then 0 else session_history.paused_counter end) ' \ - 'else 0 end) as total_duration, ' \ - 'session_history_metadata.grandparent_thumb ' \ - 'FROM session_history_metadata ' \ - 'JOIN session_history ON session_history_metadata.id = session_history.id ' \ - 'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ - '>= datetime("now", "-%s days", "localtime") ' \ - 'AND session_history_metadata.media_type = "track" ' \ - 'GROUP BY session_history_metadata.grandparent_title ' \ + query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, ' \ + 'COUNT(DISTINCT t.user_id) AS users_watched, ' \ + 'MAX(t.started) AS last_watch, COUNT(t.id) as total_plays, ' \ + 'SUM(CASE WHEN t.stopped > 0 THEN (t.stopped - t.started) ' \ + ' - (CASE WHEN t.paused_counter IS NULL THEN 0 ELSE t.paused_counter END) ELSE 0 END) ' \ + ' AS total_duration ' \ + 'FROM (SELECT * FROM session_history ' \ + ' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \ + ' WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ + ' >= datetime("now", "-%s days", "localtime") ' \ + ' AND session_history.media_type = "track" ' \ + ' GROUP BY %s) AS t ' \ + 'GROUP BY t.grandparent_title ' \ 'ORDER BY users_watched DESC, %s DESC ' \ - 'LIMIT %s' % (time_range, sort_type, stats_count) + 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) result = monitor_db.select(query) except: logger.warn("Unable to execute database query for get_home_stats: popular_music.") @@ -443,24 +424,22 @@ class DataFactory(object): elif stat == 'top_users': top_users = [] try: - query = 'SELECT session_history.user, ' \ - '(case when users.friendly_name is null then users.username else ' \ - 'users.friendly_name end) as friendly_name,' \ - 'COUNT(session_history.id) as total_plays, ' \ - 'SUM(case when session_history.stopped > 0 ' \ - 'then (session_history.stopped - session_history.started) ' \ - ' - (case when session_history.paused_counter is NULL then 0 else session_history.paused_counter end) ' \ - 'else 0 end) as total_duration, ' \ - 'MAX(session_history.started) as last_watch, ' \ - 'users.custom_avatar_url as thumb, ' \ - 'users.user_id ' \ - 'FROM session_history ' \ - 'JOIN session_history_metadata ON session_history.id = session_history_metadata.id ' \ - 'LEFT OUTER JOIN users ON session_history.user_id = users.user_id ' \ - 'WHERE datetime(session_history.stopped, "unixepoch", "localtime") >= ' \ - 'datetime("now", "-%s days", "localtime") '\ - 'GROUP BY session_history.user_id ' \ - 'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stats_count) + query = 'SELECT t.user, t.user_id, t.custom_avatar_url as thumb, ' \ + '(CASE WHEN t.friendly_name IS NULL THEN t.username ELSE t.friendly_name END) ' \ + ' AS friendly_name, ' \ + 'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, ' \ + 'SUM(CASE WHEN t.stopped > 0 THEN (t.stopped - t.started) ' \ + ' - (CASE WHEN t.paused_counter IS NULL THEN 0 ELSE t.paused_counter END) ELSE 0 END) ' \ + ' AS total_duration ' \ + 'FROM (SELECT * FROM session_history ' \ + ' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \ + ' LEFT OUTER JOIN users ON session_history.user_id = users.user_id ' \ + ' WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ + ' >= datetime("now", "-%s days", "localtime") ' \ + ' GROUP BY %s) AS t ' \ + 'GROUP BY t.user_id ' \ + 'ORDER BY %s DESC ' \ + 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) result = monitor_db.select(query) except: logger.warn("Unable to execute database query for get_home_stats: top_users.") @@ -497,18 +476,19 @@ class DataFactory(object): top_platform = [] try: - query = 'SELECT session_history.platform, ' \ - 'COUNT(session_history.id) as total_plays, ' \ - 'SUM(case when session_history.stopped > 0 ' \ - 'then (session_history.stopped - session_history.started) ' \ - ' - (case when session_history.paused_counter is NULL then 0 else session_history.paused_counter end) ' \ - 'else 0 end) as total_duration, ' \ - 'MAX(session_history.started) as last_watch ' \ - 'FROM session_history ' \ - 'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ - '>= datetime("now", "-%s days", "localtime") ' \ - 'GROUP BY session_history.platform ' \ - 'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stats_count) + query = 'SELECT t.platform, ' \ + 'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, ' \ + 'SUM(CASE WHEN t.stopped > 0 THEN (t.stopped - t.started) ' \ + ' - (CASE WHEN t.paused_counter IS NULL THEN 0 ELSE t.paused_counter END) ELSE 0 END) ' \ + ' AS total_duration ' \ + 'FROM (SELECT * FROM session_history ' \ + ' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \ + ' WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ + ' >= datetime("now", "-%s days", "localtime") ' \ + ' GROUP BY %s) AS t ' \ + 'GROUP BY t.platform ' \ + 'ORDER BY %s DESC ' \ + 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) result = monitor_db.select(query) except: logger.warn("Unable to execute database query for get_home_stats: top_platforms.") @@ -541,33 +521,26 @@ class DataFactory(object): elif stat == 'last_watched': last_watched = [] try: - query = 'SELECT session_history_metadata.id, ' \ - 'session_history.user, ' \ - '(case when users.friendly_name is null then users.username else ' \ - 'users.friendly_name end) as friendly_name,' \ - 'users.user_id, ' \ - 'users.custom_avatar_url as user_thumb, ' \ - 'session_history_metadata.full_title, ' \ - 'session_history_metadata.rating_key, ' \ - 'session_history_metadata.thumb, ' \ - 'session_history_metadata.grandparent_thumb, ' \ - 'MAX(session_history.started) as last_watch, ' \ - 'session_history.player, ' \ - '((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 ' \ - 'FROM session_history_metadata ' \ - 'JOIN session_history ON session_history_metadata.id = session_history.id ' \ - 'LEFT OUTER JOIN users ON session_history.user_id = users.user_id ' \ - 'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ - '>= datetime("now", "-%s days", "localtime") ' \ - 'AND (session_history_metadata.media_type = "movie" ' \ - 'OR session_history_metadata.media_type = "episode") ' \ - 'AND percent_complete >= %s ' \ - 'GROUP BY session_history.id ' \ + query = 'SELECT t.id, t.full_title, t.rating_key, t.thumb, t.grandparent_thumb, ' \ + 't.user, t.user_id, t.custom_avatar_url as user_thumb, t.player, ' \ + '(CASE WHEN t.friendly_name IS NULL THEN t.username ELSE t.friendly_name END) ' \ + ' AS friendly_name, ' \ + 'MAX(t.started) AS last_watch, ' \ + '((CASE WHEN t.view_offset IS NULL THEN 0.1 ELSE t.view_offset * 1.0 END) / ' \ + ' (CASE WHEN t.duration IS NULL THEN 1.0 ELSE t.duration * 1.0 END) * 100) ' \ + ' AS percent_complete ' \ + 'FROM (SELECT * FROM session_history ' \ + ' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \ + ' LEFT OUTER JOIN users ON session_history.user_id = users.user_id ' \ + ' WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ + ' >= datetime("now", "-%s days", "localtime") ' \ + ' AND (session_history.media_type = "movie" ' \ + ' OR session_history_metadata.media_type = "episode") ' \ + ' GROUP BY %s) AS t ' \ + 'WHERE percent_complete >= %s ' \ + 'GROUP BY t.id ' \ 'ORDER BY last_watch DESC ' \ - 'LIMIT %s' % (time_range, notify_watched_percent, stats_count) + 'LIMIT %s' % (time_range, group_by, notify_watched_percent, stats_count) result = monitor_db.select(query) except: logger.warn("Unable to execute database query for get_home_stats: last_watched.") @@ -1102,7 +1075,7 @@ class DataFactory(object): try: query = 'SELECT 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 total_duration ' \ + 'SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS total_duration ' \ 'FROM session_history %s ' % where result = monitor_db.select(query) except: diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 902e9286..0ee08a97 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -128,13 +128,15 @@ class WebInterface(object): def home_stats(self, **kwargs): data_factory = datafactory.DataFactory() + grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES time_range = plexpy.CONFIG.HOME_STATS_LENGTH stats_type = plexpy.CONFIG.HOME_STATS_TYPE stats_count = plexpy.CONFIG.HOME_STATS_COUNT stats_cards = plexpy.CONFIG.HOME_STATS_CARDS.split(', ') notify_watched_percent = plexpy.CONFIG.NOTIFY_WATCHED_PERCENT - stats_data = data_factory.get_home_stats(time_range=time_range, + stats_data = data_factory.get_home_stats(grouping=grouping, + time_range=time_range, stats_type=stats_type, stats_count=stats_count, stats_cards=stats_cards, @@ -613,7 +615,7 @@ class WebInterface(object): custom_where.append(['session_history.grandparent_rating_key', rating_key]) if 'start_date' in kwargs: start_date = kwargs.get('start_date', "") - custom_where.append(['strftime("%Y-%m-%d", datetime(date, "unixepoch", "localtime"))', start_date]) + custom_where.append(['strftime("%Y-%m-%d", datetime(started, "unixepoch", "localtime"))', start_date]) if 'reference_id' in kwargs: reference_id = kwargs.get('reference_id', "") custom_where.append(['session_history.reference_id', reference_id]) From 3eebb58da5adce7666edd16d5d1b7d870c45c476 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sun, 20 Dec 2015 03:14:39 -0800 Subject: [PATCH 24/25] Fix most concurrent count with duplicate time entires --- plexpy/datafactory.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index 4d7a4fdb..e1929ac8 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -607,25 +607,27 @@ class DataFactory(object): logger.warn("Unable to execute database query for get_home_stats: most_concurrent.") return None - times = {} + times = [] for item in result: - times.update({str(item['stopped']) + 'A': -1, str(item['started']) + 'B': 1}) + times.append({'time': str(item['started']) + 'B', 'count': 1}) + times.append({'time': str(item['stopped']) + 'A', 'count': -1}) + times = sorted(times, key=lambda k: k['time']) count = 0 last_start = 0 most_concurrent = {'count': count} - for key in sorted(times): - if times[key] == 1: - count += times[key] + for d in times: + if d['count'] == 1: + count += d['count'] if count >= most_concurrent['count']: - last_start = key + last_start = d['time'] else: if count >= most_concurrent['count']: most_concurrent = {'count': count, 'started': last_start[:-1], - 'stopped': key[:-1]} - count += times[key] + 'stopped': d['time'][:-1]} + count += d['count'] home_stats.append({'stat_id': stat, 'rows': [most_concurrent]}) From 45c2f50018e12a293c6fb6e99835a036b1800bdc Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sun, 20 Dec 2015 09:33:04 -0800 Subject: [PATCH 25/25] v1.2.15 --- CHANGELOG.md | 16 ++++++++++++++++ plexpy/version.py | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1c58a21..63541505 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## v1.2.15 (2015-12-20) + +* Fix navbar covering current activity on smaller screens. +* Fix metadata for grouped recently added notifications. +* Fix Growl notification agent not working. +* Change graph days selection. +* Change watch statistics to match table history grouping. +* Add automatic discovery of Pushbullet devices. +* Add Most Concurrent Streams watch statistic. +* Add precentage to current activity progress bars. +* Add a bunch of stream details to notification options. +* Add notification for Plex Remote Access/Plex Media Server back up. +* Add CC/BCC and multiple recipients to email notification agent. +* Add total watch time to history table footer. + + ## v1.2.14 (2015-12-07) * Fix regression with PlexWatch db importer and buffer warnings. diff --git a/plexpy/version.py b/plexpy/version.py index dfb6543c..f98bc36f 100644 --- a/plexpy/version.py +++ b/plexpy/version.py @@ -1,2 +1,2 @@ PLEXPY_VERSION = "master" -PLEXPY_RELEASE_VERSION = "1.2.14" +PLEXPY_RELEASE_VERSION = "1.2.15"