From 1262de2ae2390ad2bd0fc68ac0784bb4acd4f2ec Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Thu, 10 Dec 2015 21:00:00 -0800 Subject: [PATCH 01/15] Clean up notifiers --- plexpy/notifiers.py | 34 +--------------------------------- 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index c0fb44e6..18241fec 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -385,9 +385,6 @@ class GROWL(object): self.enabled = plexpy.CONFIG.GROWL_ENABLED self.host = plexpy.CONFIG.GROWL_HOST self.password = plexpy.CONFIG.GROWL_PASSWORD - self.on_play = plexpy.CONFIG.GROWL_ON_PLAY - self.on_stop = plexpy.CONFIG.GROWL_ON_STOP - self.on_watched = plexpy.CONFIG.GROWL_ON_WATCHED def conf(self, options): return cherrypy.config['config'].get('Growl', options) @@ -490,9 +487,6 @@ class PROWL(object): self.enabled = plexpy.CONFIG.PROWL_ENABLED self.keys = plexpy.CONFIG.PROWL_KEYS self.priority = plexpy.CONFIG.PROWL_PRIORITY - self.on_play = plexpy.CONFIG.PROWL_ON_PLAY - self.on_stop = plexpy.CONFIG.PROWL_ON_STOP - self.on_watched = plexpy.CONFIG.PROWL_ON_WATCHED def conf(self, options): return cherrypy.config['config'].get('Prowl', options) @@ -565,9 +559,6 @@ class XBMC(object): self.hosts = plexpy.CONFIG.XBMC_HOST self.username = plexpy.CONFIG.XBMC_USERNAME self.password = plexpy.CONFIG.XBMC_PASSWORD - self.on_play = plexpy.CONFIG.XBMC_ON_PLAY - self.on_stop = plexpy.CONFIG.XBMC_ON_STOP - self.on_watched = plexpy.CONFIG.XBMC_ON_WATCHED def _sendhttp(self, host, command): url_command = urllib.urlencode(command) @@ -648,9 +639,6 @@ class Plex(object): self.client_hosts = plexpy.CONFIG.PLEX_CLIENT_HOST self.username = plexpy.CONFIG.PLEX_USERNAME self.password = plexpy.CONFIG.PLEX_PASSWORD - self.on_play = plexpy.CONFIG.PLEX_ON_PLAY - self.on_stop = plexpy.CONFIG.PLEX_ON_STOP - self.on_watched = plexpy.CONFIG.PLEX_ON_WATCHED def _sendhttp(self, host, command): @@ -728,9 +716,6 @@ class NMA(object): def __init__(self): self.api = plexpy.CONFIG.NMA_APIKEY self.nma_priority = plexpy.CONFIG.NMA_PRIORITY - self.on_play = plexpy.CONFIG.NMA_ON_PLAY - self.on_stop = plexpy.CONFIG.NMA_ON_STOP - self.on_watched = plexpy.CONFIG.NMA_ON_WATCHED def notify(self, subject=None, message=None): if not subject or not message: @@ -790,9 +775,6 @@ class PUSHBULLET(object): self.apikey = plexpy.CONFIG.PUSHBULLET_APIKEY self.deviceid = plexpy.CONFIG.PUSHBULLET_DEVICEID self.channel_tag = plexpy.CONFIG.PUSHBULLET_CHANNEL_TAG - self.on_play = plexpy.CONFIG.PUSHBULLET_ON_PLAY - self.on_stop = plexpy.CONFIG.PUSHBULLET_ON_STOP - self.on_watched = plexpy.CONFIG.PUSHBULLET_ON_WATCHED def conf(self, options): return cherrypy.config['config'].get('PUSHBULLET', options) @@ -869,9 +851,6 @@ class PUSHALOT(object): def __init__(self): self.api_key = plexpy.CONFIG.PUSHALOT_APIKEY - self.on_play = plexpy.CONFIG.PUSHALOT_ON_PLAY - self.on_stop = plexpy.CONFIG.PUSHALOT_ON_STOP - self.on_watched = plexpy.CONFIG.PUSHALOT_ON_WATCHED def notify(self, message, event): if not message or not event: @@ -928,9 +907,6 @@ class PUSHOVER(object): self.keys = plexpy.CONFIG.PUSHOVER_KEYS self.priority = plexpy.CONFIG.PUSHOVER_PRIORITY self.sound = plexpy.CONFIG.PUSHOVER_SOUND - self.on_play = plexpy.CONFIG.PUSHOVER_ON_PLAY - self.on_stop = plexpy.CONFIG.PUSHOVER_ON_STOP - self.on_watched = plexpy.CONFIG.PUSHOVER_ON_WATCHED if plexpy.CONFIG.PUSHOVER_APITOKEN: self.application_token = plexpy.CONFIG.PUSHOVER_APITOKEN @@ -1158,9 +1134,6 @@ class TwitterNotifier(object): class OSX_NOTIFY(object): def __init__(self): - self.on_play = plexpy.CONFIG.OSX_NOTIFY_ON_PLAY - self.on_stop = plexpy.CONFIG.OSX_NOTIFY_ON_STOP - self.on_watched = plexpy.CONFIG.OSX_NOTIFY_ON_WATCHED try: self.objc = __import__("objc") self.AppKit = __import__("AppKit") @@ -1247,9 +1220,6 @@ class BOXCAR(object): self.url = 'https://new.boxcar.io/api/notifications' self.token = plexpy.CONFIG.BOXCAR_TOKEN self.sound = plexpy.CONFIG.BOXCAR_SOUND - self.on_play = plexpy.CONFIG.BOXCAR_ON_PLAY - self.on_stop = plexpy.CONFIG.BOXCAR_ON_STOP - self.on_watched = plexpy.CONFIG.BOXCAR_ON_WATCHED def notify(self, title, message): if not title or not message: @@ -1322,9 +1292,7 @@ class BOXCAR(object): class Email(object): def __init__(self): - self.on_play = plexpy.CONFIG.EMAIL_ON_PLAY - self.on_stop = plexpy.CONFIG.EMAIL_ON_STOP - self.on_watched = plexpy.CONFIG.EMAIL_ON_WATCHED + pass def notify(self, subject, message): if not subject or not message: From c042d9e39a421c0531d961cf236db5d0c11eb491 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sat, 12 Dec 2015 13:16:35 -0800 Subject: [PATCH 02/15] Fix metadata for grouped recently added notifications --- data/interfaces/default/settings.html | 3 ++- plexpy/notification_handler.py | 24 +++++++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 66a5be3d..384290f9 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -533,7 +533,8 @@ available_notification_agents = sorted(notifiers.available_notification_agents() -

Enable to only get one TV Show or Artist notification for recently added Episodes or Tracks. Movies are unaffected.

+

Enable to only get one TV Show or Artist notification for a batch of recently added Episodes or Tracks. Movies are unaffected.
+ Note: No Season/Episode or Album/Track metadata will be available.

diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index 4d4560de..fdbe3f22 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -408,6 +408,20 @@ def build_notify_text(session=None, timeline=None, state=None): progress_percent = helpers.get_percent(view_offset, duration) + # Fix metadata params for notify recently added grandparent + if plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT: + show_name = metadata['title'] + episode_name = '' + artist_name = metadata['title'] + album_name = '' + track_name = '' + else: + show_name = metadata['grandparent_title'] + episode_name = metadata['title'] + artist_name = metadata['grandparent_title'] + album_name = metadata['parent_title'] + track_name = metadata['title'] + available_params = {'server_name': server_name, 'server_uptime': server_uptime, 'user': user, @@ -416,11 +430,11 @@ def build_notify_text(session=None, timeline=None, state=None): 'ip_address': ip_address, 'media_type': metadata['media_type'], 'title': full_title, - 'show_name': metadata['grandparent_title'], - 'episode_name': metadata['title'], - 'artist_name': metadata['grandparent_title'], - 'album_name': metadata['parent_title'], - 'track_name': metadata['title'], + 'show_name': show_name, + 'episode_name': episode_name, + 'artist_name': artist_name, + 'album_name': album_name, + 'track_name': track_name, 'season_num': metadata['parent_index'].zfill(1), 'season_num00': metadata['parent_index'].zfill(2), 'episode_num': metadata['index'].zfill(1), From 6e62ffdd222995126e2bd4b3251446fab77e5706 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sat, 12 Dec 2015 14:08:34 -0800 Subject: [PATCH 03/15] Get Pushbullet devices automatically using API --- .../default/notification_config.html | 29 +++++++++++----- plexpy/notifiers.py | 33 +++++++++++++++++-- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/data/interfaces/default/notification_config.html b/data/interfaces/default/notification_config.html index 9d8ab9a1..dc997d5b 100644 --- a/data/interfaces/default/notification_config.html +++ b/data/interfaces/default/notification_config.html @@ -84,15 +84,8 @@ from plexpy import helpers % endif + + +

${top_stat['rows'][0]['count']}

+

streams

+
+ +
+
+
+ + % 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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 Popular Music + 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 05/15] 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 06/15] 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 07/15] 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 10/15] 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 11/15] 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 @@