From 048b31c87a2522aaa519435158a6054101dc9b02 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Mon, 14 Sep 2015 20:59:04 -0700 Subject: [PATCH 01/38] Fix music visible on graph only if "Log music" is enabled --- data/interfaces/default/graphs.html | 8 ++++++++ plexpy/webserve.py | 7 ++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/data/interfaces/default/graphs.html b/data/interfaces/default/graphs.html index af6ed442..8f114b67 100644 --- a/data/interfaces/default/graphs.html +++ b/data/interfaces/default/graphs.html @@ -294,6 +294,8 @@ $('a[data-toggle=tab][href=' + current_tab + ']').trigger('click'); } + var music_visible = (${config['music_logging_enable']} == 1 ? true : false); + function loadGraphsTab1(time_range, yaxis) { setGraphFormat(yaxis); @@ -319,6 +321,7 @@ hc_plays_by_day_options.yAxis.min = 0; hc_plays_by_day_options.xAxis.categories = dateArray; hc_plays_by_day_options.series = data.series; + hc_plays_by_day_options.series[2].visible = music_visible; var hc_plays_by_day = new Highcharts.Chart(hc_plays_by_day_options); } }); @@ -331,6 +334,7 @@ success: function(data) { hc_plays_by_dayofweek_options.xAxis.categories = data.categories; hc_plays_by_dayofweek_options.series = data.series; + hc_plays_by_dayofweek_options.series[2].visible = music_visible; var hc_plays_by_dayofweek = new Highcharts.Chart(hc_plays_by_dayofweek_options); } }); @@ -343,6 +347,7 @@ success: function(data) { hc_plays_by_hourofday_options.xAxis.categories = data.categories; hc_plays_by_hourofday_options.series = data.series; + hc_plays_by_hourofday_options.series[2].visible = music_visible; var hc_plays_by_hourofday = new Highcharts.Chart(hc_plays_by_hourofday_options); } }); @@ -355,6 +360,7 @@ success: function(data) { hc_plays_by_platform_options.xAxis.categories = data.categories; hc_plays_by_platform_options.series = data.series; + hc_plays_by_platform_options.series[2].visible = music_visible; var hc_plays_by_platform = new Highcharts.Chart(hc_plays_by_platform_options); } }); @@ -367,6 +373,7 @@ success: function(data) { hc_plays_by_user_options.xAxis.categories = data.categories; hc_plays_by_user_options.series = data.series; + hc_plays_by_user_options.series[2].visible = music_visible; var hc_plays_by_user = new Highcharts.Chart(hc_plays_by_user_options); } }); @@ -462,6 +469,7 @@ hc_plays_by_month_options.yAxis.min = 0; hc_plays_by_month_options.xAxis.categories = data.categories; hc_plays_by_month_options.series = data.series; + hc_plays_by_month_options.series[2].visible = music_visible; var hc_plays_by_month = new Highcharts.Chart(hc_plays_by_month_options); } }); diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 6bb6f9ac..681c0ccc 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -144,7 +144,12 @@ class WebInterface(object): @cherrypy.expose def graphs(self): - return serve_template(templatename="graphs.html", title="Graphs") + + config = { + "music_logging_enable": plexpy.CONFIG.MUSIC_LOGGING_ENABLE + } + + return serve_template(templatename="graphs.html", title="Graphs", config=config) @cherrypy.expose def sync(self): From 7170dbd800fb8195fbd5369b6380d9e2ac64433e Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Wed, 16 Sep 2015 01:13:08 -0700 Subject: [PATCH 02/38] Fix home stats Last Watched to use watched percent specified in settings --- data/interfaces/default/index.html | 12 +++++++++--- plexpy/datafactory.py | 11 ++++++++--- plexpy/webserve.py | 8 ++++++-- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html index b73bdbe0..ed983ca6 100644 --- a/data/interfaces/default/index.html +++ b/data/interfaces/default/index.html @@ -82,12 +82,15 @@ currentActivity(); setInterval(currentActivity, 15000); - function getHomeStats(days, stat_type, stat_count) { + function getHomeStats(days, stat_type, stat_count, notify_watched_percent) { $.ajax({ url: 'home_stats', cache: false, async: true, - data: {time_range: days, stat_type: stat_type, stat_count: stat_count}, + data: {time_range: days, + stat_type: stat_type, + stat_count: stat_count, + notify_watched_percent: notify_watched_percent}, complete: function(xhr, status) { $("#home-stats").html(xhr.responseText); } @@ -165,7 +168,10 @@ } }); - getHomeStats(${config['home_stats_length']}, ${config['home_stats_type']}, ${config['home_stats_count']}); + getHomeStats(${config['home_stats_length']}, + ${config['home_stats_type']}, + ${config['home_stats_count']}, + ${config['notify_watched_percent']}); getLibraryStatsHeader(); getLibraryStats(); diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index 4a988480..5412922d 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -131,7 +131,7 @@ class DataFactory(object): return dict - def get_home_stats(self, time_range='30', stat_type='0', stat_count='5'): + def get_home_stats(self, time_range='30', stat_type='0', stat_count='5', notify_watched_percent='85'): monitor_db = database.MonitorDatabase() if not time_range.isdigit(): @@ -432,7 +432,11 @@ class DataFactory(object): 'session_history_metadata.thumb, ' \ 'session_history_metadata.grandparent_thumb, ' \ 'MAX(session_history.started) as last_watch, ' \ - 'session_history.player as platform ' \ + 'session_history.player as platform, ' \ + '((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 ' \ @@ -440,9 +444,10 @@ class DataFactory(object): '>= 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_metadata.full_title ' \ 'ORDER BY last_watch DESC ' \ - 'LIMIT %s' % (time_range, stat_count) + 'LIMIT %s' % (time_range, notify_watched_percent, stat_count) result = monitor_db.select(query) except: logger.warn("Unable to execute database query.") diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 681c0ccc..de5af414 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -69,6 +69,7 @@ class WebInterface(object): "home_stats_type": plexpy.CONFIG.HOME_STATS_TYPE, "home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT, "pms_identifier": plexpy.CONFIG.PMS_IDENTIFIER, + "notify_watched_percent": plexpy.CONFIG.NOTIFY_WATCHED_PERCENT } return serve_template(templatename="index.html", title="Home", config=config) @@ -121,9 +122,12 @@ class WebInterface(object): return json.dumps(formats) @cherrypy.expose - def home_stats(self, time_range='30', stat_type='0', stat_count='5', **kwargs): + def home_stats(self, time_range='30', stat_type='0', stat_count='5', notify_watched_percent='85', **kwargs): data_factory = datafactory.DataFactory() - stats_data = data_factory.get_home_stats(time_range=time_range, stat_type=stat_type, stat_count=stat_count) + stats_data = data_factory.get_home_stats(time_range=time_range, + stat_type=stat_type, + stat_count=stat_count, + notify_watched_percent=notify_watched_percent) return serve_template(templatename="home_stats.html", title="Stats", data=stats_data) From 1f3a238ab28f9bcbdbaf843aaa9e7df2e3639711 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Wed, 16 Sep 2015 15:42:45 -0700 Subject: [PATCH 03/38] Fix logic to add "Direct Stream" state to current activity --- data/interfaces/default/current_activity.html | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/data/interfaces/default/current_activity.html b/data/interfaces/default/current_activity.html index 04632966..00a96048 100644 --- a/data/interfaces/default/current_activity.html +++ b/data/interfaces/default/current_activity.html @@ -119,6 +119,8 @@ DOCUMENTATION :: END % if a['type'] == 'track': % if a['audio_decision'] == 'direct play': Stream  Direct Play + % elif a['audio_decision'] == 'copy': + Stream  Direct Stream % else: Stream  Transcoding (Speed: ${a['transcode_speed']}) @@ -136,8 +138,10 @@ DOCUMENTATION :: END Audio  Transcode (${a['transcode_audio_codec']}) (${a['transcode_audio_channels']}ch) % endif % elif a['type'] == 'episode' or a['type'] == 'movie' or a['type'] == 'clip': - % if a['video_decision'] == 'direct play': + % if a['video_decision'] == 'direct play' and a['audio_decision'] == 'direct play': Stream  Direct Play + % elif a['video_decision'] == 'copy' and a['audio_decision'] == 'copy': + Stream  Direct Stream % else: Stream  Transcoding (Speed: ${a['transcode_speed']}) @@ -165,6 +169,8 @@ DOCUMENTATION :: END % elif a['type'] == 'photo': % if a['video_decision'] == 'direct play': Stream  Direct Play + % elif a['video_decision'] == 'copy': + Stream  Direct Stream % else: Stream   Transcoding From 483f5825db400d96e28cf29f3e8854730438bdd2 Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 17 Sep 2015 21:04:10 +0200 Subject: [PATCH 04/38] Add check for webbrowser import which may not be available in all Python builds (like certain NAS devices). --- plexpy/__init__.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/plexpy/__init__.py b/plexpy/__init__.py index ad02333c..fc9639c4 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -17,11 +17,16 @@ import os import sys import subprocess import threading -import webbrowser import sqlite3 import cherrypy import datetime import uuid +# Some cut down versions of Python may not include this module and it's not critical for us +try: + import webbrowser + no_browser = False +except ImportError: + no_browser = True from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.interval import IntervalTrigger @@ -228,18 +233,19 @@ def daemonize(): def launch_browser(host, port, root): - if host == '0.0.0.0': - host = 'localhost' + if not no_browser: + if host == '0.0.0.0': + host = 'localhost' - if CONFIG.ENABLE_HTTPS: - protocol = 'https' - else: - protocol = 'http' + if CONFIG.ENABLE_HTTPS: + protocol = 'https' + else: + protocol = 'http' - try: - webbrowser.open('%s://%s:%i%s' % (protocol, host, port, root)) - except Exception as e: - logger.error('Could not launch browser: %s', e) + try: + webbrowser.open('%s://%s:%i%s' % (protocol, host, port, root)) + except Exception as e: + logger.error('Could not launch browser: %s', e) def initialize_scheduler(): From 6eec4d1ca6d02f1f6644aeefd2a4f5d4de474e53 Mon Sep 17 00:00:00 2001 From: Tim Date: Sat, 19 Sep 2015 00:57:24 +0200 Subject: [PATCH 05/38] Some Twitter love. Resolves Issue #47. --- .../default/notification_config.html | 34 ++++++++- plexpy/config.py | 8 +- plexpy/notifiers.py | 75 ++++++++++++++----- 3 files changed, 95 insertions(+), 22 deletions(-) diff --git a/data/interfaces/default/notification_config.html b/data/interfaces/default/notification_config.html index 58f6436e..41e4a341 100644 --- a/data/interfaces/default/notification_config.html +++ b/data/interfaces/default/notification_config.html @@ -9,9 +9,9 @@
-
+
% for item in data: - % if item['input_type'] != 'checkbox': + % if item['input_type'] == 'text' or item['input_type'] == 'number' or item['input_type'] == 'password':
@@ -20,6 +20,11 @@ % endif

${item['description']}

+ % elif item['input_type'] == 'button': +
+ +
+

${item['description']}

% elif item['input_type'] == 'checkbox':
@@ -53,4 +65,20 @@ doAjaxCall('set_notification_config',$(this),'tabs',true); return false; }); + + $('#twitterStep1').click(function () { + $.get("/twitterStep1", function (data) {window.open(data); }) + .done(function () { $('#ajaxMsg').html("
Confirm Authorization. Check pop-up blocker if no response.
"); }); + $('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut(); + }); + $('#twitterStep2').click(function () { + var twitter_key = $("#twitter_key").val(); + $.get("/twitterStep2", {'key': twitter_key}, function (data) { $('#ajaxMsg').html("
"+data+"
"); }); + $('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut(); + }); + $('#testTwitter').click(function () { + $.get("/testTwitter", + function (data) { $('#ajaxMsg').html("
"+data+"
"); }); + $('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut(); + }); diff --git a/plexpy/config.py b/plexpy/config.py index 51145685..9d41b67a 100644 --- a/plexpy/config.py +++ b/plexpy/config.py @@ -194,8 +194,14 @@ _CONFIG_DEFINITIONS = { 'TV_NOTIFY_ON_PAUSE': (int, 'Monitoring', 0), 'TWITTER_ENABLED': (int, 'Twitter', 0), 'TWITTER_PASSWORD': (str, 'Twitter', ''), - 'TWITTER_PREFIX': (str, 'Twitter', 'Headphones'), + 'TWITTER_PREFIX': (str, 'Twitter', 'PlexPy'), 'TWITTER_USERNAME': (str, 'Twitter', ''), + 'TWITTER_ON_PLAY': (int, 'Twitter', 0), + 'TWITTER_ON_STOP': (int, 'Twitter', 0), + 'TWITTER_ON_PAUSE': (int, 'Twitter', 0), + 'TWITTER_ON_RESUME': (int, 'Twitter', 0), + 'TWITTER_ON_BUFFER': (int, 'Twitter', 0), + 'TWITTER_ON_WATCHED': (int, 'Twitter', 0), 'UPDATE_DB_INTERVAL': (int, 'General', 24), 'VERIFY_SSL_CERT': (bool_int, 'Advanced', 1), 'VIDEO_LOGGING_ENABLE': (int, 'Monitoring', 1), diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index 2c348b5a..6d3abda1 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -49,7 +49,8 @@ AGENT_IDS = {"Growl": 0, "Pushover": 7, "OSX Notify": 8, "Boxcar2": 9, - "Email": 10} + "Email": 10, + "Twitter": 11} def available_notification_agents(): agents = [{'name': 'Growl', @@ -171,6 +172,18 @@ def available_notification_agents(): 'on_resume': plexpy.CONFIG.EMAIL_ON_RESUME, 'on_buffer': plexpy.CONFIG.EMAIL_ON_BUFFER, 'on_watched': plexpy.CONFIG.EMAIL_ON_WATCHED + }, + {'name': 'Twitter', + 'id': AGENT_IDS['Twitter'], + 'config_prefix': 'twitter', + 'has_config': True, + 'state': checked(plexpy.CONFIG.TWITTER_ENABLED), + 'on_play': plexpy.CONFIG.TWITTER_ON_PLAY, + 'on_stop': plexpy.CONFIG.TWITTER_ON_STOP, + 'on_pause': plexpy.CONFIG.TWITTER_ON_PAUSE, + 'on_resume': plexpy.CONFIG.TWITTER_ON_RESUME, + 'on_buffer': plexpy.CONFIG.TWITTER_ON_BUFFER, + 'on_watched': plexpy.CONFIG.TWITTER_ON_WATCHED } ] @@ -229,6 +242,9 @@ def get_notification_agent_config(config_id): elif config_id == 10: email = Email() return email.return_config_options() + elif config_id == 11: + tweet = TwitterNotifier() + return tweet.return_config_options() else: return [] else: @@ -271,6 +287,9 @@ def send_notification(config_id, subject, body): elif config_id == 10: email = Email() email.notify(subject=subject, message=body) + elif config_id == 11: + tweet = TwitterNotifier() + tweet.notify(subject=subject, message=body) else: logger.debug(u"PlexPy Notifier :: Unknown agent id received.") else: @@ -912,19 +931,17 @@ class TwitterNotifier(object): SIGNIN_URL = 'https://api.twitter.com/oauth/authenticate' def __init__(self): - self.consumer_key = "oYKnp2ddX5gbARjqX8ZAAg" - self.consumer_secret = "A4Xkw9i5SjHbTk7XT8zzOPqivhj9MmRDR9Qn95YA9sk" + self.consumer_key = "2LdJKXHDUwJtjYBsdwJisIOsh" + self.consumer_secret = "QWbUcZzAIiL4zbDCIhy2EdUkV8yEEav3qMdo5y3FugxCFelWrA" - def notify_snatch(self, title): - if plexpy.CONFIG.TWITTER_ONSNATCH: - self._notifyTwitter(common.notifyStrings[common.NOTIFY_SNATCH] + ': ' + title + ' at ' + helpers.now()) - - def notify_download(self, title): - if plexpy.CONFIG.TWITTER_ENABLED: - self._notifyTwitter(common.notifyStrings[common.NOTIFY_DOWNLOAD] + ': ' + title + ' at ' + helpers.now()) + def notify(self, subject, message): + if not subject or not message: + return + else: + self._send_tweet(subject + ': ' + message) def test_notify(self): - return self._notifyTwitter("This is a test notification from PlexPy at " + helpers.now(), force=True) + return self._send_tweet("This is a test notification from PlexPy at " + helpers.now()) def _get_authorization(self): @@ -979,7 +996,6 @@ class TwitterNotifier(object): return True def _send_tweet(self, message=None): - username = self.consumer_key password = self.consumer_secret access_token_key = plexpy.CONFIG.TWITTER_USERNAME @@ -997,13 +1013,36 @@ class TwitterNotifier(object): return True - def _notifyTwitter(self, message='', force=False): - prefix = plexpy.CONFIG.TWITTER_PREFIX + def return_config_options(self): + config_option = [{'label': 'Request Authorisation', + 'value': 'Request Authorisation', + 'name': 'twitterStep1', + 'description': 'Step 1: Click Request button above. (Ensure you allow the browser pop-up).', + 'input_type': 'button' + }, + {'label': 'Authorisation Key', + 'value': '', + 'name': 'twitter_key', + 'description': 'Step 2: Input the authorisation key you received from Step 1.', + 'input_type': 'text' + }, + {'label': 'Verify Key', + 'value': 'Verify Key', + 'name': 'twitterStep2', + 'description': 'Step 3: Verify the key.', + 'input_type': 'button' + }, + {'label': 'Test Twitter', + 'value': 'Test Twitter', + 'name': 'testTwitter', + 'description': 'Test if Twitter notifications are working. See logs for troubleshooting.', + 'input_type': 'button' + }, + {'input_type': 'nosave' + } + ] - if not plexpy.CONFIG.TWITTER_ENABLED and not force: - return False - - return self._send_tweet(prefix + ": " + message) + return config_option class OSX_NOTIFY(object): From 20056718dbe7475d09ab76be8fca743943c1577f Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Fri, 18 Sep 2015 18:52:46 -0700 Subject: [PATCH 06/38] Add setting to selectively hide watch statistic cards on homepage --- data/interfaces/default/css/plexpy.css | 27 ++++++++++ data/interfaces/default/index.html | 2 + data/interfaces/default/settings.html | 75 +++++++++++++++++++------- plexpy/config.py | 1 + plexpy/webserve.py | 5 ++ 5 files changed, 91 insertions(+), 19 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index ff44dbea..649f36ac 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -33,6 +33,33 @@ select.input-sm { color: #999; outline: none; } +select[multiple] { + height: 125px; + margin: 5px 0 5px 0; + color: #fff; + border: 0px solid #444; + background: #555; + padding: 2px 2px; + background-color: #555; + border-radius: 3px; + transition: background-color .3s; +} +select[multiple]:focus { + outline: 0; + outline: thin dotted \9; + color: #555; + background-color: #fff; + transition: background-color .3s; +} +select[multiple]:focus::-webkit-scrollbar-thumb { + background-color: rgba(0,0,0,.15); +} +select[multiple] option { + padding: 6px 10px; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; +} img { -webkit-box-sizing: content-box; -moz-box-sizing: content-box; diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html index ed983ca6..fab5d5b3 100644 --- a/data/interfaces/default/index.html +++ b/data/interfaces/default/index.html @@ -16,6 +16,7 @@
+ % if config['home_stats_cards'] > 'watch_statistics':
@@ -27,6 +28,7 @@
+ % endif
diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index d13e8700..ee37f5fd 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -34,14 +34,15 @@ available_notification_agents = notifiers.available_notification_agents()
@@ -83,10 +84,33 @@ available_notification_agents = notifiers.available_notification_agents()

Set your preferred time format. Click here to see the parameter list.

+

+
+
-

Homepage Statistics

+

Watch Statistics

+
+ +

Select the cards to show in the watch statistics on the home page.

+
+
+ +
+
+
@@ -95,7 +119,7 @@ available_notification_agents = notifiers.available_notification_agents()
-

Specify the number of days for the statistics on the home page. Default is 30 days.

+

Specify the number of days for the watch statistics on the home page. Default is 30 days.

@@ -105,7 +129,7 @@ available_notification_agents = notifiers.available_notification_agents()
-

Specify the number of items to show in the top lists for the statistics on the home page. Max is 10 items, default is 5 items, 0 to disable.

+

Specify the number of items to show in the top lists for the watch statistics on the home page. Max is 10 items, default is 5 items, 0 to disable.

-
+

Web Interface

@@ -164,7 +188,7 @@ available_notification_agents = notifiers.available_notification_agents()

-
+

Authentication

@@ -216,7 +240,7 @@ available_notification_agents = notifiers.available_notification_agents()

-
+

Plex Media Server

@@ -272,7 +296,7 @@ available_notification_agents = notifiers.available_notification_agents()
-
+

Plex.tv Authentication

@@ -315,7 +339,7 @@ available_notification_agents = notifiers.available_notification_agents()

-
+

Extra Settings

@@ -334,7 +358,7 @@ available_notification_agents = notifiers.available_notification_agents()

-
+

Monitoring Settings

@@ -416,7 +440,7 @@ available_notification_agents = notifiers.available_notification_agents()

-
+

Global Notification Toggles

@@ -566,7 +590,7 @@ available_notification_agents = notifiers.available_notification_agents()

-
+

Notification Agents

@@ -1178,6 +1202,19 @@ $(document).ready(function() { } var accordion = new Accordion($('#accordion'), false); + + var cards = "${config['home_stats_cards']}".split(/[\s,]+/); + cards.forEach(function (item) { + $('#card-'+item).prop('selected', !$(this).prop('selected')); + }); + $('#home_stats_cards').on('mousedown', function(e) { + e.preventDefault(); + var scroll = this.scrollTop; + e.target.selected = !e.target.selected; + this.scrollTop = scroll; + }).on('mousemove', function(e) { + e.preventDefault() + }); }); diff --git a/plexpy/config.py b/plexpy/config.py index 9d41b67a..7288ac34 100644 --- a/plexpy/config.py +++ b/plexpy/config.py @@ -85,6 +85,7 @@ _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', 'top_tv, popular_tv, top_movies, popular_movies, top_music, popular_music, top_users, top_platforms, last_watched'), 'HTTPS_CERT': (str, 'General', ''), 'HTTPS_KEY': (str, 'General', ''), 'HTTP_HOST': (str, 'General', '0.0.0.0'), diff --git a/plexpy/webserve.py b/plexpy/webserve.py index de5af414..c1507dee 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -472,6 +472,7 @@ class WebInterface(object): "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, + "home_stats_cards": plexpy.CONFIG.HOME_STATS_CARDS, "buffer_threshold": plexpy.CONFIG.BUFFER_THRESHOLD, "buffer_wait": plexpy.CONFIG.BUFFER_WAIT } @@ -524,6 +525,10 @@ class WebInterface(object): if kwargs['pms_ip'] != plexpy.CONFIG.PMS_IP: refresh_users = True + if 'home_stats_cards' in kwargs: + if kwargs['home_stats_cards'] != 'watch_statistics': + kwargs['home_stats_cards'] = ', '.join(kwargs['home_stats_cards']) + plexpy.CONFIG.process_kwargs(kwargs) # Write the config From 78f959d39a8c2d2d0b2a0e680c465c9d1fbf9232 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Fri, 18 Sep 2015 18:54:48 -0700 Subject: [PATCH 07/38] Clean up passing unnecessary configs to homepage --- data/interfaces/default/index.html | 12 +++------ plexpy/datafactory.py | 43 +++++++++++++++--------------- plexpy/webserve.py | 20 +++++++++----- 3 files changed, 38 insertions(+), 37 deletions(-) diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html index fab5d5b3..e18cfdf8 100644 --- a/data/interfaces/default/index.html +++ b/data/interfaces/default/index.html @@ -84,15 +84,12 @@ currentActivity(); setInterval(currentActivity, 15000); - function getHomeStats(days, stat_type, stat_count, notify_watched_percent) { + function getHomeStats(days) { $.ajax({ url: 'home_stats', cache: false, async: true, - data: {time_range: days, - stat_type: stat_type, - stat_count: stat_count, - notify_watched_percent: notify_watched_percent}, + data: { }, complete: function(xhr, status) { $("#home-stats").html(xhr.responseText); } @@ -170,10 +167,7 @@ } }); - getHomeStats(${config['home_stats_length']}, - ${config['home_stats_type']}, - ${config['home_stats_count']}, - ${config['notify_watched_percent']}); + getHomeStats(); getLibraryStatsHeader(); getLibraryStats(); diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index 5412922d..ebf7f339 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -131,19 +131,12 @@ class DataFactory(object): return dict - def get_home_stats(self, time_range='30', stat_type='0', stat_count='5', notify_watched_percent='85'): + def get_home_stats(self, time_range='30', stats_type='0', stats_count='5', stats_cards='', notify_watched_percent='85'): monitor_db = database.MonitorDatabase() - if not time_range.isdigit(): - time_range = '30' + sort_type = 'total_plays' if stats_type == '0' else 'total_duration' - sort_type = 'total_plays' if stat_type == '0' else 'total_duration' - - if not time_range.isdigit(): - stat_count = '5' - - # This actually determines the output order in the home page - stats_queries = ["top_tv", "popular_tv", "top_movies", "popular_movies", "top_users", "top_platforms", "last_watched"] + stats_queries = stats_cards.split(', ') home_stats = [] for stat in stats_queries: @@ -166,7 +159,7 @@ class DataFactory(object): '>= 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, stat_count) + 'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stats_count) result = monitor_db.select(query) except: logger.warn("Unable to execute database query.") @@ -202,6 +195,10 @@ class DataFactory(object): '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 ' \ @@ -209,8 +206,8 @@ class DataFactory(object): '>= datetime("now", "-%s days", "localtime") ' \ 'AND session_history_metadata.media_type = "episode" ' \ 'GROUP BY session_history_metadata.grandparent_title ' \ - 'ORDER BY users_watched DESC, total_plays DESC ' \ - 'LIMIT %s' % (time_range, stat_count) + 'ORDER BY users_watched DESC, %s DESC ' \ + 'LIMIT %s' % (time_range, sort_type, stats_count) result = monitor_db.select(query) except: logger.warn("Unable to execute database query.") @@ -222,7 +219,7 @@ class DataFactory(object): 'rating_key': item[3], 'last_play': item[4], 'total_plays': item[5], - 'grandparent_thumb': item[6], + 'grandparent_thumb': item[7], 'thumb': '', 'user': '', 'friendly_name': '', @@ -254,7 +251,7 @@ class DataFactory(object): '>= 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, stat_count) + 'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stats_count) result = monitor_db.select(query) except: logger.warn("Unable to execute database query.") @@ -290,6 +287,10 @@ class DataFactory(object): '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 ' \ @@ -297,8 +298,8 @@ class DataFactory(object): '>= datetime("now", "-%s days", "localtime") ' \ 'AND session_history_metadata.media_type = "movie" ' \ 'GROUP BY session_history_metadata.full_title ' \ - 'ORDER BY users_watched DESC, total_plays DESC ' \ - 'LIMIT %s' % (time_range, stat_count) + 'ORDER BY users_watched DESC, %s DESC ' \ + 'LIMIT %s' % (time_range, sort_type, stats_count) result = monitor_db.select(query) except: logger.warn("Unable to execute database query.") @@ -311,7 +312,7 @@ class DataFactory(object): 'last_play': item[4], 'total_plays': item[5], 'grandparent_thumb': '', - 'thumb': item[6], + 'thumb': item[7], 'user': '', 'friendly_name': '', 'platform_type': '', @@ -343,7 +344,7 @@ class DataFactory(object): '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, stat_count) + 'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stats_count) result = monitor_db.select(query) except: logger.warn("Unable to execute database query.") @@ -391,7 +392,7 @@ class DataFactory(object): '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, stat_count) + 'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stats_count) result = monitor_db.select(query) except: logger.warn("Unable to execute database query.") @@ -447,7 +448,7 @@ class DataFactory(object): 'AND percent_complete >= %s ' \ 'GROUP BY session_history_metadata.full_title ' \ 'ORDER BY last_watch DESC ' \ - 'LIMIT %s' % (time_range, notify_watched_percent, stat_count) + 'LIMIT %s' % (time_range, notify_watched_percent, stats_count) result = monitor_db.select(query) except: logger.warn("Unable to execute database query.") diff --git a/plexpy/webserve.py b/plexpy/webserve.py index c1507dee..42dd50e0 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -66,10 +66,8 @@ class WebInterface(object): def home(self): config = { "home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH, - "home_stats_type": plexpy.CONFIG.HOME_STATS_TYPE, - "home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT, - "pms_identifier": plexpy.CONFIG.PMS_IDENTIFIER, - "notify_watched_percent": plexpy.CONFIG.NOTIFY_WATCHED_PERCENT + "home_stats_cards": plexpy.CONFIG.HOME_STATS_CARDS, + "pms_identifier": plexpy.CONFIG.PMS_IDENTIFIER } return serve_template(templatename="index.html", title="Home", config=config) @@ -122,11 +120,19 @@ class WebInterface(object): return json.dumps(formats) @cherrypy.expose - def home_stats(self, time_range='30', stat_type='0', stat_count='5', notify_watched_percent='85', **kwargs): + def home_stats(self, **kwargs): data_factory = datafactory.DataFactory() + + 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 + notify_watched_percent = plexpy.CONFIG.NOTIFY_WATCHED_PERCENT + stats_data = data_factory.get_home_stats(time_range=time_range, - stat_type=stat_type, - stat_count=stat_count, + stats_type=stats_type, + stats_count=stats_count, + stats_cards=stats_cards, notify_watched_percent=notify_watched_percent) return serve_template(templatename="home_stats.html", title="Stats", data=stats_data) From 8a989d71ca2a743530c2ae23bd50e7845d7e02e2 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Fri, 18 Sep 2015 18:58:59 -0700 Subject: [PATCH 08/38] Add top music and popular music cards to watch statistics --- data/interfaces/default/home_stats.html | 160 +++++++++++++++++++++++- plexpy/datafactory.py | 92 ++++++++++++++ 2 files changed, 248 insertions(+), 4 deletions(-) diff --git a/data/interfaces/default/home_stats.html b/data/interfaces/default/home_stats.html index 01fb0cdd..36c17d7f 100644 --- a/data/interfaces/default/home_stats.html +++ b/data/interfaces/default/home_stats.html @@ -17,19 +17,19 @@ data[array_index]['rows'] :: Usable parameters row_id Return the db row id for a metadata item if one exists -== Only if 'stat_id' is 'top_tv' or 'popular_tv' or 'top_movies' or 'popular_movies' or 'last_watched' == +== Only if 'stat_id' is 'top_tv' or 'popular_tv' or 'top_movies' or 'popular_movies' or 'top_music' or 'popular_music' or 'last_watched' == thumb Return the thumb for the media item. -== Only if 'stat_id' is 'top_tv' or 'popular_tv' == +== Only if 'stat_id' is 'top_tv' or 'popular_tv' or 'top_music' or 'popular_music' == grandparent_thumb Returns location of the item's thumbnail. Use with pms_image_proxy. rating_key Returns the unique identifier for the media item. title Returns the title for the associated stat. -== Only if 'stat_id' is 'top_tv' or 'top_movies' or 'top_user' or 'top_platform' == +== Only if 'stat_id' is 'top_tv' or 'top_movies' or 'top_music' or 'top_user' or 'top_platform' == total_plays Returns the count for the associated stat. total_duration Returns the total duration for the associated stat. -== Only of 'stat_id' is 'popular_tv' or 'popular_movies' == +== Only of 'stat_id' is 'popular_tv' or 'popular_movies' or 'popular_music' == users_watched Returns the count for the associated stat. == Only if 'stat_id' is 'top_user' or 'last_watched' == @@ -372,6 +372,158 @@ DOCUMENTATION :: END % endif
+ % elif top_stat['stat_id'] == 'top_music' and top_stat['rows']: +
+
  • +
    +
    +

    Most Listened to Artist

    +
    +
    +

    + + ${top_stat['rows'][0]['title']} + +

    + % if top_stat['stat_type'] == 'total_plays': +

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

    +

    plays

    + % else: + ${top_stat['rows'][0]['total_duration'] | hd} + % endif +
    +
    + + % if top_stat['rows'][0]['grandparent_thumb']: +
    +
    +
    + % else: +
    +
    +
    + % endif +
    + %if len(top_stat['rows']) > 1: +
    + + % endif +
  • +
    + % elif top_stat['stat_id'] == 'popular_music' and top_stat['rows']: +
    +
  • +
    +
    +

    Most Popular Artist

    +
    +
    +

    + + ${top_stat['rows'][0]['title']} + +

    +

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

    +

    users

    +
    +
    + + % if top_stat['rows'][0]['grandparent_thumb'] != '': +
    +
    +
    + % else: +
    +
    +
    + % endif +
    + %if len(top_stat['rows']) > 1: +
    + + % endif +
  • +
    % elif top_stat['stat_id'] == 'top_users' and top_stat['rows']:
  • diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index ebf7f339..83698d6e 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -324,6 +324,98 @@ class DataFactory(object): home_stats.append({'stat_id': stat, 'rows': popular_movies}) + elif 'top_music' in stat: + 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) + result = monitor_db.select(query) + except: + logger.warn("Unable to execute database query.") + return None + + for item in result: + row = {'title': item[1], + 'total_plays': item[2], + 'total_duration': item[3], + 'users_watched': '', + 'rating_key': item[4], + 'last_play': item[5], + 'grandparent_thumb': item[6], + 'thumb': '', + 'user': '', + 'friendly_name': '', + 'platform_type': '', + 'platform': '', + 'row_id': item[0] + } + top_music.append(row) + + home_stats.append({'stat_id': stat, + 'stat_type': sort_type, + 'rows': top_music}) + + elif 'popular_music' in stat: + 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 ' \ + 'ORDER BY users_watched DESC, %s DESC ' \ + 'LIMIT %s' % (time_range, sort_type, stats_count) + result = monitor_db.select(query) + except: + logger.warn("Unable to execute database query.") + return None + + for item in result: + row = {'title': item[1], + 'users_watched': item[2], + 'rating_key': item[3], + 'last_play': item[4], + 'total_plays': item[5], + 'grandparent_thumb': item[7], + 'thumb': '', + 'user': '', + 'friendly_name': '', + 'platform_type': '', + 'platform': '', + 'row_id': item[0] + } + popular_music.append(row) + + home_stats.append({'stat_id': stat, + 'rows': popular_music}) + elif 'top_users' in stat: top_users = [] try: From 078f4babf5750d69b36ee217dd8b8693ea7ee537 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Fri, 18 Sep 2015 23:42:16 -0700 Subject: [PATCH 09/38] Add setting to selectively hide library statistics cards on the homepage --- data/interfaces/default/index.html | 2 ++ data/interfaces/default/settings.html | 47 ++++++++++++++++++++++++++- plexpy/config.py | 3 +- plexpy/pmsconnect.py | 13 +++++--- plexpy/webserve.py | 31 +++++++++++++++++- 5 files changed, 89 insertions(+), 7 deletions(-) diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html index e18cfdf8..5f8caeba 100644 --- a/data/interfaces/default/index.html +++ b/data/interfaces/default/index.html @@ -29,6 +29,7 @@
  • % endif + % if config['home_library_cards'] > 'library_statistics':
    @@ -40,6 +41,7 @@
    + % endif
    diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index ee37f5fd..394c3d63 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -93,7 +93,7 @@ available_notification_agents = notifiers.available_notification_agents()
    -

    Select the cards to show in the watch statistics on the home page.

    +

    Select the cards to show in the watch statistics on the home page. Select none to disable.

    + + +
    +
    +

    +
    @@ -1215,6 +1232,34 @@ $(document).ready(function() { }).on('mousemove', function(e) { e.preventDefault() }); + + $.ajax({ + url: 'get_server_children', + data: { }, + async: true, + complete: function (xhr, status) { + server_children_info = $.parseJSON(xhr.responseText); + libraries_list = server_children_info.libraries_list; + for (var i in libraries_list) { + title = libraries_list[i].title; + key = libraries_list[i].key; + $('#home_library_cards').append('') + } + var cards = "${config['home_library_cards']}".split(/[\s,]+/); + cards.forEach(function (item) { + $('#card-'+item).prop('selected', !$(this).prop('selected')); + }); + } + }); + $('#home_library_cards').on('mousedown', function(e) { + e.preventDefault(); + var scroll = this.scrollTop; + e.target.selected = !e.target.selected; + this.scrollTop = scroll; + }).on('mousemove', function(e) { + e.preventDefault() + }); + }); diff --git a/plexpy/config.py b/plexpy/config.py index 7288ac34..c48a1858 100644 --- a/plexpy/config.py +++ b/plexpy/config.py @@ -82,10 +82,11 @@ _CONFIG_DEFINITIONS = { 'GROWL_ON_RESUME': (int, 'Growl', 0), 'GROWL_ON_BUFFER': (int, 'Growl', 0), 'GROWL_ON_WATCHED': (int, 'Growl', 0), + 'HOME_LIBRARY_CARDS': (str, 'General', 'library_statistics_first'), 'HOME_STATS_LENGTH': (int, 'General', 30), 'HOME_STATS_TYPE': (int, 'General', 0), 'HOME_STATS_COUNT': (int, 'General', 5), - 'HOME_STATS_CARDS': (str, 'General', '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_first'), 'HTTPS_CERT': (str, 'General', ''), 'HTTPS_KEY': (str, 'General', ''), 'HTTP_HOST': (str, 'General', '0.0.0.0'), diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index 25b23253..97b159e2 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -1208,7 +1208,7 @@ class PmsConnect(object): 'title': helpers.get_xml_attr(xml_head[0], 'title1'), 'libraries_list': libraries_list } - + return output """ @@ -1270,13 +1270,15 @@ class PmsConnect(object): return output """ - Return processed and validated server statistics. + Return processed and validated library statistics. Output: array """ - def get_library_stats(self): + def get_library_stats(self, library_cards=''): server_libraries = self.get_server_children() + library_keys = library_cards.split(', ') + server_library_stats = [] if server_libraries['libraries_count'] != '0': @@ -1285,7 +1287,10 @@ class PmsConnect(object): for library in libraries_list: library_type = library['type'] section_key = library['key'] - library_list = self.get_library_children(library_type, section_key) + if section_key in library_keys: + library_list = self.get_library_children(library_type, section_key) + else: + continue if library_list['library_count'] != '0': library_stats = {'title': library['title'], diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 42dd50e0..5bc15399 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -67,6 +67,7 @@ class WebInterface(object): config = { "home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH, "home_stats_cards": plexpy.CONFIG.HOME_STATS_CARDS, + "home_library_cards": plexpy.CONFIG.HOME_LIBRARY_CARDS, "pms_identifier": plexpy.CONFIG.PMS_IDENTIFIER } return serve_template(templatename="index.html", title="Home", config=config) @@ -140,7 +141,10 @@ class WebInterface(object): @cherrypy.expose def library_stats(self, **kwargs): pms_connect = pmsconnect.PmsConnect() - stats_data = pms_connect.get_library_stats() + + library_cards = plexpy.CONFIG.HOME_LIBRARY_CARDS + + stats_data = pms_connect.get_library_stats(library_cards=library_cards) return serve_template(templatename="library_stats.html", title="Library Stats", data=stats_data) @@ -479,6 +483,7 @@ class WebInterface(object): "home_stats_type": checked(plexpy.CONFIG.HOME_STATS_TYPE), "home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT, "home_stats_cards": plexpy.CONFIG.HOME_STATS_CARDS, + "home_library_cards": plexpy.CONFIG.HOME_LIBRARY_CARDS, "buffer_threshold": plexpy.CONFIG.BUFFER_THRESHOLD, "buffer_wait": plexpy.CONFIG.BUFFER_WAIT } @@ -535,6 +540,10 @@ class WebInterface(object): if kwargs['home_stats_cards'] != 'watch_statistics': kwargs['home_stats_cards'] = ', '.join(kwargs['home_stats_cards']) + if 'home_library_cards' in kwargs: + if kwargs['home_library_cards'] != 'library_statistics': + kwargs['home_library_cards'] = ', '.join(kwargs['home_library_cards']) + plexpy.CONFIG.process_kwargs(kwargs) # Write the config @@ -1132,6 +1141,26 @@ class WebInterface(object): else: logger.warn('Unable to retrieve data.') + @cherrypy.expose + def get_server_children(self, **kwargs): + + pms_connect = pmsconnect.PmsConnect() + result = pms_connect.get_server_children() + + if plexpy.CONFIG.HOME_LIBRARY_CARDS == '': + library_keys = ['library_statistics'] + for library in result['libraries_list']: + library_keys.append(library['key']) + + plexpy.CONFIG.HOME_LIBRARY_CARDS = ', '.join(library_keys) + plexpy.CONFIG.write() + + if result: + cherrypy.response.headers['Content-type'] = 'application/json' + return json.dumps(result) + else: + logger.warn('Unable to retrieve data.') + @cherrypy.expose def get_activity(self, **kwargs): From ad183ff9fe91ab72947b7a59ea19b7e9f2a3ca80 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Fri, 18 Sep 2015 23:43:01 -0700 Subject: [PATCH 10/38] Update message to prompt the user to enable cards for homepage statistics --- data/interfaces/default/home_stats.html | 2 +- data/interfaces/default/library_stats.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/interfaces/default/home_stats.html b/data/interfaces/default/home_stats.html index 36c17d7f..85f83496 100644 --- a/data/interfaces/default/home_stats.html +++ b/data/interfaces/default/home_stats.html @@ -802,6 +802,6 @@ DOCUMENTATION :: END
    No stats for selected period.

    % endif % else: -
    Unable to retrieve data from database. Please check your settings. +
    No cards to show. Please enable cards in the settings.

    % endif \ No newline at end of file diff --git a/data/interfaces/default/library_stats.html b/data/interfaces/default/library_stats.html index 34ef2a91..aef22736 100644 --- a/data/interfaces/default/library_stats.html +++ b/data/interfaces/default/library_stats.html @@ -73,6 +73,6 @@ DOCUMENTATION :: END % endfor % else: -
    Unable to retrieve data from database. Please check your settings. +
    No cards to show. Please enable cards in the settings.

    % endif \ No newline at end of file From d61e699dc98050a6d4a0668daa4ce2a15dc7af04 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Fri, 18 Sep 2015 23:49:34 -0700 Subject: [PATCH 11/38] Remove test code --- plexpy/webserve.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 5bc15399..be877bd9 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -1146,14 +1146,6 @@ class WebInterface(object): pms_connect = pmsconnect.PmsConnect() result = pms_connect.get_server_children() - - if plexpy.CONFIG.HOME_LIBRARY_CARDS == '': - library_keys = ['library_statistics'] - for library in result['libraries_list']: - library_keys.append(library['key']) - - plexpy.CONFIG.HOME_LIBRARY_CARDS = ', '.join(library_keys) - plexpy.CONFIG.write() if result: cherrypy.response.headers['Content-type'] = 'application/json' From de4d8fb277265a11f7188741b2987fc39bcd1d6a Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sat, 19 Sep 2015 00:42:00 -0700 Subject: [PATCH 12/38] Default to show all cards until the user disables them. --- data/interfaces/default/home_stats.html | 2 +- data/interfaces/default/library_stats.html | 2 +- plexpy/config.py | 2 +- plexpy/datafactory.py | 3 +-- plexpy/pmsconnect.py | 4 +--- plexpy/webserve.py | 15 +++++++++++++-- 6 files changed, 18 insertions(+), 10 deletions(-) diff --git a/data/interfaces/default/home_stats.html b/data/interfaces/default/home_stats.html index 85f83496..36c17d7f 100644 --- a/data/interfaces/default/home_stats.html +++ b/data/interfaces/default/home_stats.html @@ -802,6 +802,6 @@ DOCUMENTATION :: END
    No stats for selected period.

    % endif % else: -
    No cards to show. Please enable cards in the settings. +
    Unable to retrieve data from database. Please check your settings.

    % endif \ No newline at end of file diff --git a/data/interfaces/default/library_stats.html b/data/interfaces/default/library_stats.html index aef22736..12e7135c 100644 --- a/data/interfaces/default/library_stats.html +++ b/data/interfaces/default/library_stats.html @@ -73,6 +73,6 @@ DOCUMENTATION :: END % endfor % else: -
    No cards to show. Please enable cards in the settings. +
    Unable to retrieve data from server. Please check your settings.

    % endif \ No newline at end of file diff --git a/plexpy/config.py b/plexpy/config.py index c48a1858..73034be2 100644 --- a/plexpy/config.py +++ b/plexpy/config.py @@ -86,7 +86,7 @@ _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_first'), + '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'), '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 83698d6e..52d21e42 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -136,10 +136,9 @@ class DataFactory(object): sort_type = 'total_plays' if stats_type == '0' else 'total_duration' - stats_queries = stats_cards.split(', ') home_stats = [] - for stat in stats_queries: + for stat in stats_cards: if 'top_tv' in stat: top_tv = [] try: diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index 97b159e2..0fa2e061 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -1277,8 +1277,6 @@ class PmsConnect(object): def get_library_stats(self, library_cards=''): server_libraries = self.get_server_children() - library_keys = library_cards.split(', ') - server_library_stats = [] if server_libraries['libraries_count'] != '0': @@ -1287,7 +1285,7 @@ class PmsConnect(object): for library in libraries_list: library_type = library['type'] section_key = library['key'] - if section_key in library_keys: + if section_key in library_cards: library_list = self.get_library_children(library_type, section_key) else: continue diff --git a/plexpy/webserve.py b/plexpy/webserve.py index be877bd9..2851605d 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -127,7 +127,7 @@ class WebInterface(object): 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 + 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, @@ -142,7 +142,18 @@ class WebInterface(object): def library_stats(self, **kwargs): pms_connect = pmsconnect.PmsConnect() - library_cards = plexpy.CONFIG.HOME_LIBRARY_CARDS + library_cards = plexpy.CONFIG.HOME_LIBRARY_CARDS.split(', ') + + if library_cards == ['library_statistics_first']: + library_cards = ['library_statistics'] + server_children = pms_connect.get_server_children() + server_libraries = server_children['libraries_list'] + + for library in server_libraries: + library_cards.append(library['key']) + + plexpy.CONFIG.HOME_LIBRARY_CARDS = ', '.join(library_cards) + plexpy.CONFIG.write() stats_data = pms_connect.get_library_stats(library_cards=library_cards) From 924ed70458499c60d03681eb912010780c4543c9 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sat, 19 Sep 2015 00:53:18 -0700 Subject: [PATCH 13/38] Move cards settings help text --- data/interfaces/default/settings.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 394c3d63..90a0c185 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -93,7 +93,6 @@ available_notification_agents = notifiers.available_notification_agents()
    -

    Select the cards to show in the watch statistics on the home page. Select none to disable.

    +

    Select the cards to show in the watch statistics on the home page. Select none to disable.

    @@ -144,7 +144,6 @@ available_notification_agents = notifiers.available_notification_agents()
    -

    Select the cards to show in the library statistics on the home page. Select none to disable.

    +

    Select the cards to show in the library statistics on the home page. Select none to disable.

    From 7c6619ebc533f72e9536ce7a85835fdce19d7a0c Mon Sep 17 00:00:00 2001 From: Tim Date: Sat, 19 Sep 2015 12:37:51 +0200 Subject: [PATCH 14/38] Fix Email TLS checkbox bug. Clean up some notifier code. --- .../default/notification_config.html | 16 +++++- plexpy/notifiers.py | 2 +- plexpy/webserve.py | 56 +------------------ 3 files changed, 17 insertions(+), 57 deletions(-) diff --git a/data/interfaces/default/notification_config.html b/data/interfaces/default/notification_config.html index 41e4a341..8e685d89 100644 --- a/data/interfaces/default/notification_config.html +++ b/data/interfaces/default/notification_config.html @@ -1,3 +1,6 @@ +<%! +from plexpy import helpers +%> % if data: -

    The interval (in seconds) PlexPy will wait for a video item to be active before logging it. 0 to disable.

    +

    The interval (in seconds) an item must be in a playing state before logging it. 0 to disable.

    diff --git a/data/interfaces/default/home_stats.html b/data/interfaces/default/home_stats.html index fd5f96e1..3eeba036 100644 --- a/data/interfaces/default/home_stats.html +++ b/data/interfaces/default/home_stats.html @@ -77,7 +77,7 @@ DOCUMENTATION :: END

    - + ${top_stat['rows'][0]['title']}

    @@ -89,7 +89,7 @@ DOCUMENTATION :: END % endif
    - + % if top_stat['rows'][0]['grandparent_thumb']: - + % if top_stat['rows'][0]['grandparent_thumb'] != '': - + % if top_stat['rows'][0]['thumb']: - + % if top_stat['rows'][0]['thumb']: - + % if top_stat['rows'][0]['grandparent_thumb']: - + % if top_stat['rows'][0]['grandparent_thumb'] != '': -
    +
    @@ -652,7 +652,7 @@ DOCUMENTATION :: END
  • -
    +
    ${top_stat['rows'][loop.index]['platform_type']}
    @@ -665,7 +665,7 @@ DOCUMENTATION :: END % endif
  • -
    - + % if top_stat['rows'][0]['thumb']:
    -

    ${child['title']}

    +

    ${child['title']}

    % elif data['children_type'] == 'album':
    -

    ${child['title']}

    +

    ${child['title']}

    % elif data['children_type'] == 'track': % if loop.index % 2 == 0: diff --git a/data/interfaces/default/recently_added.html b/data/interfaces/default/recently_added.html index 86736961..4f11db4b 100644 --- a/data/interfaces/default/recently_added.html +++ b/data/interfaces/default/recently_added.html @@ -44,10 +44,10 @@ DOCUMENTATION :: END
    % if item['type'] == 'season': -

    ${item['parent_title']}

    +

    ${item['parent_title']}

    ${item['title']}

    % elif item['type'] == 'movie': -

    ${item['title']}

    +

    ${item['title']}

    ${item['year']}

    % endif
    @@ -66,7 +66,7 @@ DOCUMENTATION :: END
    -

    ${item['parent_title']}

    +

    ${item['parent_title']}

    ${item['title']}

    diff --git a/data/interfaces/default/user_recently_watched.html b/data/interfaces/default/user_recently_watched.html index b87cae83..2e432203 100644 --- a/data/interfaces/default/user_recently_watched.html +++ b/data/interfaces/default/user_recently_watched.html @@ -48,11 +48,11 @@ DOCUMENTATION :: END
    % if item['type'] == 'episode': -

    ${item['grandparent_title']}

    -

    ${item['title']}

    +

    ${item['grandparent_title']}

    +

    ${item['title']}

    S${item['parent_index']} · E${item['index']}

    % elif item['type'] == 'movie': -

    ${item['title']}

    +

    ${item['title']}

    ${item['year']}

    % endif
    @@ -73,8 +73,8 @@ DOCUMENTATION :: END
    -

    ${item['grandparent_title']}

    -

    ${item['title']}

    +

    ${item['grandparent_title']}

    +

    ${item['title']}

    ${item['parent_title']}

    From ed6ff9c10c53c86b43bb095c431eb0d4d3febc2a Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 23 Sep 2015 19:23:17 +0200 Subject: [PATCH 30/38] Don't fire off session start until the item is playing (fixes double start notifications). Don't try to write a temp session if we don't have any data. --- plexpy/activity_handler.py | 8 ++- plexpy/activity_processor.py | 112 +++++++++++++++++------------------ 2 files changed, 61 insertions(+), 59 deletions(-) diff --git a/plexpy/activity_handler.py b/plexpy/activity_handler.py index e1e3d42d..1f8298fe 100644 --- a/plexpy/activity_handler.py +++ b/plexpy/activity_handler.py @@ -170,11 +170,12 @@ class ActivityHandler(object): ap = activity_processor.ActivityProcessor() db_session = ap.get_session_by_key(session_key=self.get_session_key()) + this_state = self.timeline['state'] + this_key = str(self.timeline['ratingKey']) + # If we already have this session in the temp table, check for state changes if db_session: - this_state = self.timeline['state'] last_state = db_session['state'] - this_key = str(self.timeline['ratingKey']) last_key = str(db_session['rating_key']) # Make sure the same item is being played @@ -210,4 +211,5 @@ class ActivityHandler(object): else: # We don't have this session in our table yet, start a new one. - self.on_start() \ No newline at end of file + if this_state != 'buffering': + self.on_start() \ No newline at end of file diff --git a/plexpy/activity_processor.py b/plexpy/activity_processor.py index 8fa2345c..c82ea51f 100644 --- a/plexpy/activity_processor.py +++ b/plexpy/activity_processor.py @@ -27,69 +27,69 @@ class ActivityProcessor(object): self.db = database.MonitorDatabase() def write_session(self, session=None): + if session: + values = {'session_key': session['session_key'], + 'rating_key': session['rating_key'], + 'media_type': session['media_type'], + 'state': session['state'], + 'user_id': session['user_id'], + 'user': session['user'], + 'machine_id': session['machine_id'], + 'title': session['title'], + 'parent_title': session['parent_title'], + 'grandparent_title': session['grandparent_title'], + 'friendly_name': session['friendly_name'], + 'player': session['player'], + 'platform': session['platform'], + 'parent_rating_key': session['parent_rating_key'], + 'grandparent_rating_key': session['grandparent_rating_key'], + 'view_offset': session['view_offset'], + 'duration': session['duration'], + 'video_decision': session['video_decision'], + 'audio_decision': session['audio_decision'], + 'width': session['width'], + 'height': session['height'], + 'container': session['container'], + 'video_codec': session['video_codec'], + 'audio_codec': session['audio_codec'], + 'bitrate': session['bitrate'], + 'video_resolution': session['video_resolution'], + 'video_framerate': session['video_framerate'], + 'aspect_ratio': session['aspect_ratio'], + 'audio_channels': session['audio_channels'], + 'transcode_protocol': session['transcode_protocol'], + 'transcode_container': session['transcode_container'], + 'transcode_video_codec': session['transcode_video_codec'], + 'transcode_audio_codec': session['transcode_audio_codec'], + 'transcode_audio_channels': session['transcode_audio_channels'], + 'transcode_width': session['transcode_width'], + 'transcode_height': session['transcode_height'] + } - values = {'session_key': session['session_key'], - 'rating_key': session['rating_key'], - 'media_type': session['media_type'], - 'state': session['state'], - 'user_id': session['user_id'], - 'user': session['user'], - 'machine_id': session['machine_id'], - 'title': session['title'], - 'parent_title': session['parent_title'], - 'grandparent_title': session['grandparent_title'], - 'friendly_name': session['friendly_name'], - 'player': session['player'], - 'platform': session['platform'], - 'parent_rating_key': session['parent_rating_key'], - 'grandparent_rating_key': session['grandparent_rating_key'], - 'view_offset': session['view_offset'], - 'duration': session['duration'], - 'video_decision': session['video_decision'], - 'audio_decision': session['audio_decision'], - 'width': session['width'], - 'height': session['height'], - 'container': session['container'], - 'video_codec': session['video_codec'], - 'audio_codec': session['audio_codec'], - 'bitrate': session['bitrate'], - 'video_resolution': session['video_resolution'], - 'video_framerate': session['video_framerate'], - 'aspect_ratio': session['aspect_ratio'], - 'audio_channels': session['audio_channels'], - 'transcode_protocol': session['transcode_protocol'], - 'transcode_container': session['transcode_container'], - 'transcode_video_codec': session['transcode_video_codec'], - 'transcode_audio_codec': session['transcode_audio_codec'], - 'transcode_audio_channels': session['transcode_audio_channels'], - 'transcode_width': session['transcode_width'], - 'transcode_height': session['transcode_height'] - } + keys = {'session_key': session['session_key'], + 'rating_key': session['rating_key']} - keys = {'session_key': session['session_key'], - 'rating_key': session['rating_key']} + result = self.db.upsert('sessions', values, keys) - result = self.db.upsert('sessions', values, keys) + if result == 'insert': + # Push any notifications - Push it on it's own thread so we don't hold up our db actions + threading.Thread(target=notification_handler.notify, + kwargs=dict(stream_data=values,notify_action='play')).start() - if result == 'insert': - # Push any notifications - Push it on it's own thread so we don't hold up our db actions - threading.Thread(target=notification_handler.notify, - kwargs=dict(stream_data=values,notify_action='play')).start() + started = int(time.time()) - started = int(time.time()) + # Try and grab IP address from logs + if plexpy.CONFIG.IP_LOGGING_ENABLE and plexpy.CONFIG.PMS_LOGS_FOLDER: + ip_address = self.find_session_ip(rating_key=session['rating_key'], + machine_id=session['machine_id']) + else: + ip_address = None - # Try and grab IP address from logs - if plexpy.CONFIG.IP_LOGGING_ENABLE and plexpy.CONFIG.PMS_LOGS_FOLDER: - ip_address = self.find_session_ip(rating_key=session['rating_key'], - machine_id=session['machine_id']) - else: - ip_address = None + timestamp = {'started': started, + 'ip_address': ip_address} - timestamp = {'started': started, - 'ip_address': ip_address} - - # If it's our first write then time stamp it. - self.db.upsert('sessions', timestamp, keys) + # If it's our first write then time stamp it. + self.db.upsert('sessions', timestamp, keys) def write_session_history(self, session=None, import_metadata=None, is_import=False, import_ignore_interval=0): from plexpy import users From 89a03fb263f2ad1aa4c68fff2db188db79f4acde Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 23 Sep 2015 19:40:22 +0200 Subject: [PATCH 31/38] Fix the real reason we were getting double start notifications. --- plexpy/activity_handler.py | 2 +- plexpy/activity_processor.py | 7 ++++--- plexpy/notifiers.py | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/plexpy/activity_handler.py b/plexpy/activity_handler.py index 1f8298fe..675eb891 100644 --- a/plexpy/activity_handler.py +++ b/plexpy/activity_handler.py @@ -51,7 +51,7 @@ class ActivityHandler(object): def update_db_session(self): # Update our session temp table values monitor_proc = activity_processor.ActivityProcessor() - monitor_proc.write_session(self.get_live_session()) + monitor_proc.write_session(session=self.get_live_session(), notify=False) def on_start(self): if self.is_valid_session(): diff --git a/plexpy/activity_processor.py b/plexpy/activity_processor.py index c82ea51f..ed926254 100644 --- a/plexpy/activity_processor.py +++ b/plexpy/activity_processor.py @@ -26,7 +26,7 @@ class ActivityProcessor(object): def __init__(self): self.db = database.MonitorDatabase() - def write_session(self, session=None): + def write_session(self, session=None, notify=True): if session: values = {'session_key': session['session_key'], 'rating_key': session['rating_key'], @@ -73,8 +73,9 @@ class ActivityProcessor(object): if result == 'insert': # Push any notifications - Push it on it's own thread so we don't hold up our db actions - threading.Thread(target=notification_handler.notify, - kwargs=dict(stream_data=values,notify_action='play')).start() + if notify: + threading.Thread(target=notification_handler.notify, + kwargs=dict(stream_data=values, notify_action='play')).start() started = int(time.time()) diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index e780fef7..adc24a38 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -1106,6 +1106,7 @@ class OSX_NOTIFY(object): notification_center = NSUserNotificationCenter.defaultUserNotificationCenter() notification_center.deliverNotification_(notification) + logger.info(u"OSX Notify notifications sent.") del pool return True From 6f6541fdb8a4243e110356caceb99e43d95064f4 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 23 Sep 2015 22:57:35 +0200 Subject: [PATCH 32/38] Start the scheduler after first run wizard. --- plexpy/webserve.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plexpy/webserve.py b/plexpy/webserve.py index f9ee45fd..519577c7 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -99,6 +99,7 @@ class WebInterface(object): # The setup wizard just refreshes the page on submit so we must redirect to home if config set. # Also redirecting to home if a PMS token already exists - will remove this in future. if plexpy.CONFIG.FIRST_RUN_COMPLETE or plexpy.CONFIG.PMS_TOKEN: + plexpy.initialize_scheduler() raise cherrypy.HTTPRedirect("home") else: return serve_template(templatename="welcome.html", title="Welcome", config=config) From f901eaa6256db4e74fe6442181527aecaa10cb97 Mon Sep 17 00:00:00 2001 From: Hellowlol Date: Fri, 25 Sep 2015 01:11:32 +0200 Subject: [PATCH 33/38] Open compare link in new tab --- data/interfaces/default/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/interfaces/default/base.html b/data/interfaces/default/base.html index 96aa9c81..d0ec31bf 100644 --- a/data/interfaces/default/base.html +++ b/data/interfaces/default/base.html @@ -37,7 +37,7 @@ from plexpy import version % elif plexpy.CONFIG.CHECK_GITHUB and plexpy.CURRENT_VERSION != plexpy.LATEST_VERSION and plexpy.COMMITS_BEHIND > 0 and plexpy.INSTALL_TYPE != 'win': From f921035ee76ba2e42b38305c51a0f61f2d3e5df3 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Thu, 24 Sep 2015 18:18:07 -0700 Subject: [PATCH 34/38] Fix ajax message alignment on datatables --- data/interfaces/default/js/tables/history_table.js | 2 +- data/interfaces/default/js/tables/history_table_modal.js | 2 +- data/interfaces/default/js/tables/logs.js | 2 +- data/interfaces/default/js/tables/user_ips.js | 2 +- data/interfaces/default/js/tables/users.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/interfaces/default/js/tables/history_table.js b/data/interfaces/default/js/tables/history_table.js index 1cccbf9b..ba214107 100644 --- a/data/interfaces/default/js/tables/history_table.js +++ b/data/interfaces/default/js/tables/history_table.js @@ -237,7 +237,7 @@ history_table_options = { } }, "preDrawCallback": function(settings) { - var msg = "
     Fetching rows...
    "; + var msg = " Fetching rows..."; showMsg(msg, false, false, 0) }, "rowCallback": function (row, rowData) { diff --git a/data/interfaces/default/js/tables/history_table_modal.js b/data/interfaces/default/js/tables/history_table_modal.js index efb109c3..770ecf47 100644 --- a/data/interfaces/default/js/tables/history_table_modal.js +++ b/data/interfaces/default/js/tables/history_table_modal.js @@ -134,7 +134,7 @@ history_table_modal_options = { }); }, "preDrawCallback": function(settings) { - var msg = "
     Fetching rows...
    "; + var msg = " Fetching rows..."; showMsg(msg, false, false, 0) } } diff --git a/data/interfaces/default/js/tables/logs.js b/data/interfaces/default/js/tables/logs.js index cfcd3a57..50cf2cef 100644 --- a/data/interfaces/default/js/tables/logs.js +++ b/data/interfaces/default/js/tables/logs.js @@ -35,7 +35,7 @@ var log_table_options = { $('#ajaxMsg').fadeOut(); }, "preDrawCallback": function(settings) { - var msg = "
     Fetching rows...
    "; + var msg = " Fetching rows..."; showMsg(msg, false, false, 0) } } diff --git a/data/interfaces/default/js/tables/user_ips.js b/data/interfaces/default/js/tables/user_ips.js index bb2667d4..de008320 100644 --- a/data/interfaces/default/js/tables/user_ips.js +++ b/data/interfaces/default/js/tables/user_ips.js @@ -123,7 +123,7 @@ user_ip_table_options = { }, "preDrawCallback": function(settings) { - var msg = "
     Fetching rows...
    "; + var msg = " Fetching rows..."; showMsg(msg, false, false, 0) } } diff --git a/data/interfaces/default/js/tables/users.js b/data/interfaces/default/js/tables/users.js index f47daf0f..04bf631b 100644 --- a/data/interfaces/default/js/tables/users.js +++ b/data/interfaces/default/js/tables/users.js @@ -187,7 +187,7 @@ users_list_table_options = { } }, "preDrawCallback": function(settings) { - var msg = "
     Fetching rows...
    "; + var msg = " Fetching rows..."; showMsg(msg, false, false, 0) }, "rowCallback": function (row, rowData) { From e9585ea15dc786194f0ef07e84e5e52ce5405afc Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Fri, 25 Sep 2015 11:55:22 -0700 Subject: [PATCH 35/38] Use .png Plex logo instead of .svg * Fix .svg not showing on some devices --- data/interfaces/default/css/plexpy.css | 2 +- .../default/images/plex-logo-light-small.png | Bin 0 -> 1441 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 data/interfaces/default/images/plex-logo-light-small.png diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 649f36ac..a5bdd369 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -1101,7 +1101,7 @@ a:hover .dashboard-recent-media-cover { display: block; width: 100%; height: 100%; - background-image: url(../images/plex-logo-light.svg); + background-image: url(../images/plex-logo-light-small.png); background-size: 100px; background-repeat: no-repeat; background-position: center; diff --git a/data/interfaces/default/images/plex-logo-light-small.png b/data/interfaces/default/images/plex-logo-light-small.png new file mode 100644 index 0000000000000000000000000000000000000000..d2c711cda0435583bd9d598e35f35aed0c146395 GIT binary patch literal 1441 zcmV;S1z!4zP)_amia<@Bf>f0>yS%qMYtOECcgAZ6g6*fg?1y(}X6McC_j@zD%t3>6)IMBCT=dF8;k+)jML?ea*c6AU`Pq>0WNAMi62#b_8B!_F=c8h}a22qJ{s zK8j!1BClX@%GzPmcRPRR6auA7B#A=`Z~-M zqTUD{Q>1em71XeTXZDDwH+m?piESxG{qXHu`!Au8=h~v4wQ&n1>K&oyP74Dwd*8Ev zwM5(Mg3gnSuARkt-Fvc$aE<6Fti%+Ym?UI*;!eMk|CDn22zH3NN{EJfhq40b@b?X* zFB*6yeEvm^V31Yhe&Q(}&=RAlqPR2%Lu5x!{((q_YL~#vLhp%uar28O@(GL(NOQ3- ztMUon$H-e2a7)U(hRe7^T4J@eBSPdU^MdW`=QY#ZLRS@iHn9mKWRJdjz17LOf&$+` ziUanoU*Oi|H+6KjG3bDdoE=%(B`AQtshe?ckX&Y_0#gI`tf*Ha3T1Z9>FXc@ zVu`$ZPtufbD%XG-tch1u*gQ#T_WkB#IHeM|0wksd*Tcl2HYln*n z2s%;K!>`Npk1$GR{|!+edi&|@jDaXvaU}63dI~rOgU>t(lMlWeJFgV;xp6=SN>x#A zd)vF`-cha~h!u} zFEC)cfBsuQdf$-;^(%@MISo$y>SRz^mg1|EG}iu7Ww$;hmF?@(2j&@f>Y1z)`AMnt zS!wnD!k;DSxr6k!E_AdD$XXFy?Al_=t_GYh>E8qifSXVVt=aX=LI)f)$wk&be{ zi2K6by$_|sCvHY98x?eD+h6Az2PDBRo;sAn&MwELw*J0tvzqzhRxO71X-3}AsBVeg vH;n_L7TgG`3ZSHg=Ts=rL5Z6N|0BQv^3JB4R%>E<00000NkvXXu0mjfx8=60 literal 0 HcmV?d00001 From dcb7486fd6807e99a9a20d41f97532e76dc0f7b0 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Fri, 25 Sep 2015 12:13:00 -0700 Subject: [PATCH 36/38] Fix audio transcode/stream/play icon on history tables --- data/interfaces/default/js/tables/history_table.js | 9 +++++---- data/interfaces/default/js/tables/history_table_modal.js | 6 +++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/data/interfaces/default/js/tables/history_table.js b/data/interfaces/default/js/tables/history_table.js index ba214107..7fd7662d 100644 --- a/data/interfaces/default/js/tables/history_table.js +++ b/data/interfaces/default/js/tables/history_table.js @@ -98,14 +98,15 @@ history_table_options = { "createdCell": function (td, cellData, rowData, row, col) { if (cellData !== '') { var transcode_dec = ''; - if (rowData['video_decision'] === 'transcode') { + if (rowData['video_decision'] === 'transcode' || rowData['audio_decision'] === 'transcode') { transcode_dec = ''; - } else if (rowData['video_decision'] === 'copy') { + } else if (rowData['video_decision'] === 'copy' || rowData['audio_decision'] === 'copy') { transcode_dec = ''; - } else if (rowData['video_decision'] === 'direct play' || rowData['video_decision'] === '') { + } else if (rowData['video_decision'] === 'direct play' || rowData['audio_decision'] === 'direct play') { transcode_dec = ''; + } else { + $(td).html(''); } - $(td).html(''); } }, "width": "15%", diff --git a/data/interfaces/default/js/tables/history_table_modal.js b/data/interfaces/default/js/tables/history_table_modal.js index 770ecf47..c4536867 100644 --- a/data/interfaces/default/js/tables/history_table_modal.js +++ b/data/interfaces/default/js/tables/history_table_modal.js @@ -77,11 +77,11 @@ history_table_modal_options = { "createdCell": function (td, cellData, rowData, row, col) { if (cellData !== '') { var transcode_dec = ''; - if (rowData['video_decision'] === 'transcode') { + if (rowData['video_decision'] === 'transcode' || rowData['audio_decision'] === 'transcode') { transcode_dec = ''; - } else if (rowData['video_decision'] === 'copy') { + } else if (rowData['video_decision'] === 'copy' || rowData['audio_decision'] === 'copy') { transcode_dec = ''; - } else if (rowData['video_decision'] === 'direct play' || rowData['video_decision'] === '') { + } else if (rowData['video_decision'] === 'direct play' || rowData['audio_decision'] === 'direct play') { transcode_dec = ''; } $(td).html(''); From 7327be1ff87e113b2bd6f4f8550bbe60d6fd5a80 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sun, 27 Sep 2015 10:26:35 -0700 Subject: [PATCH 37/38] Pass audio_decision to history tables --- data/interfaces/default/js/tables/history_table.js | 5 ++--- plexpy/datafactory.py | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/data/interfaces/default/js/tables/history_table.js b/data/interfaces/default/js/tables/history_table.js index 7fd7662d..ae303bf6 100644 --- a/data/interfaces/default/js/tables/history_table.js +++ b/data/interfaces/default/js/tables/history_table.js @@ -104,9 +104,8 @@ history_table_options = { transcode_dec = ''; } else if (rowData['video_decision'] === 'direct play' || rowData['audio_decision'] === 'direct play') { transcode_dec = ''; - } else { - $(td).html(''); - } + } + $(td).html(''); } }, "width": "15%", diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index 2343ce42..3daf0062 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -60,6 +60,7 @@ class DataFactory(object): 'session_history.user', 'session_history_metadata.media_type', 'session_history_media_info.video_decision', + 'session_history_media_info.audio_decision', 'session_history.user_id as user_id' ] try: @@ -118,6 +119,7 @@ class DataFactory(object): "user": item["user"], "media_type": item["media_type"], "video_decision": item["video_decision"], + "audio_decision": item["audio_decision"], "user_id": item["user_id"] } From 6e2fa9a3b4980bec109410804d05c2de6d269533 Mon Sep 17 00:00:00 2001 From: Tim Date: Sun, 27 Sep 2015 20:48:10 +0200 Subject: [PATCH 38/38] Fix plexWatch db importer. Always return None on exception in http handler. Increase websocket retry counts. --- plexpy/http_handler.py | 3 ++- plexpy/plexwatch_import.py | 15 ++++++++------- plexpy/web_socket.py | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/plexpy/http_handler.py b/plexpy/http_handler.py index dc75a5c9..44a2b10b 100644 --- a/plexpy/http_handler.py +++ b/plexpy/http_handler.py @@ -81,10 +81,11 @@ class HTTPHandler(object): logger.warn(u"Failed to access uri endpoint %s with error %s" % (uri, e)) return None except Exception, e: - logger.warn(u"Failed to access uri endpoint %s. Is your server maybe accepting SSL connections only?" % uri) + logger.warn(u"Failed to access uri endpoint %s. Is your server maybe accepting SSL connections only? %s" % (uri, e)) return None except: logger.warn(u"Failed to access uri endpoint %s with Uncaught exception." % uri) + return None if request_status == 200: if output_format == 'dict': diff --git a/plexpy/plexwatch_import.py b/plexpy/plexwatch_import.py index 39f48c74..7df5b355 100644 --- a/plexpy/plexwatch_import.py +++ b/plexpy/plexwatch_import.py @@ -15,7 +15,7 @@ import sqlite3 -from plexpy import logger, helpers, monitor, users, plextv +from plexpy import logger, helpers, activity_pinger, activity_processor, users, plextv from xml.dom import minidom import plexpy @@ -245,9 +245,10 @@ def import_from_plexwatch(database=None, table_name=None, import_ignore_interval logger.debug(u"PlexPy Importer :: PlexWatch data import in progress...") logger.debug(u"PlexPy Importer :: Disabling monitoring while import in progress.") - plexpy.schedule_job(monitor.check_active_sessions, 'Check for active sessions', hours=0, minutes=0, seconds=0) + plexpy.schedule_job(activity_pinger.check_active_sessions, 'Check for active sessions', + hours=0, minutes=0, seconds=0) - monitor_processing = monitor.MonitorProcessing() + ap = activity_processor.ActivityProcessor() user_data = users.Users() # Get the latest friends list so we can pull user id's @@ -373,10 +374,10 @@ def import_from_plexwatch(database=None, table_name=None, import_ignore_interval # On older versions of PMS, "clip" items were still classified as "movie" and had bad ratingKey values # Just make sure that the ratingKey is indeed an integer if session_history_metadata['rating_key'].isdigit(): - monitor_processing.write_session_history(session=session_history, - import_metadata=session_history_metadata, - is_import=True, - import_ignore_interval=import_ignore_interval) + ap.write_session_history(session=session_history, + import_metadata=session_history_metadata, + is_import=True, + import_ignore_interval=import_ignore_interval) else: logger.debug(u"PlexPy Importer :: Item has bad rating_key: %s" % session_history_metadata['rating_key']) diff --git a/plexpy/web_socket.py b/plexpy/web_socket.py index ffefc37f..79ec4b34 100644 --- a/plexpy/web_socket.py +++ b/plexpy/web_socket.py @@ -50,7 +50,7 @@ def run(): reconnects = 0 # Try an open the websocket connection - if it fails after 5 retries fallback to polling - while not ws_connected and reconnects < 5: + while not ws_connected and reconnects < 15: try: logger.info(u'PlexPy WebSocket :: Opening websocket, connection attempt %s.' % str(reconnects + 1)) ws = create_connection(uri)