diff --git a/PlexPy.py b/PlexPy.py index 6c31d888..008ccec5 100755 --- a/PlexPy.py +++ b/PlexPy.py @@ -149,7 +149,7 @@ def main(): 'Cannot write to the data directory: ' + plexpy.DATA_DIR + '. Exiting...') # Put the database in the DATA_DIR - # plexpy.DB_FILE = os.path.join(plexpy.DATA_DIR, 'plexpy.db') + plexpy.DB_FILE = os.path.join(plexpy.DATA_DIR, 'plexpy.db') # Read config and start logging plexpy.initialize(config_file) diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index 72f00e33..dc582d75 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -40,7 +40,7 @@
@@ -206,13 +206,13 @@
+ -

OS X

@@ -537,22 +493,13 @@
-
-
-

MPC

-
-
- Enable MPC Update -
-

- + ---> @@ -885,26 +832,6 @@ } }); - if ($("#lms").is(":checked")) - { - $("#lmsoptions").show(); - } - else - { - $("#lmsoptions").hide(); - } - - $("#lms").click(function(){ - if ($("#lms").is(":checked")) - { - $("#lmsoptions").slideDown(); - } - else - { - $("#lmsoptions").slideUp(); - } - }); - if ($("#plex").is(":checked")) { $("#plexoptions").show(); @@ -1066,26 +993,6 @@ } }); - if ($("#subsonic").is(":checked")) - { - $("#subsonicoptions").show(); - } - else - { - $("#subsonicoptions").hide(); - } - - $("#subsonic").click(function(){ - if ($("#subsonic").is(":checked")) - { - $("#subsonicoptions").slideDown(); - } - else - { - $("#subsonicoptions").slideUp(); - } - }); - if ($("#email").is(":checked")) { $("#email_options").show(); diff --git a/data/interfaces/default/current_activity.html b/data/interfaces/default/current_activity.html index dbd6d3b3..26b44c5f 100644 --- a/data/interfaces/default/current_activity.html +++ b/data/interfaces/default/current_activity.html @@ -29,13 +29,8 @@ player Returns the name of the platform used to play the stream audioDecision Returns the audio transcode decision. Either 'transcode', 'copy' or 'direct play'. audioCodec Returns the name of the audio codec. audioChannels Returns the number of audio channels. - -== Only if 'type' is 'episode' == -grandparentTitle Returns the name of the TV Show. - -== Only if 'type' is 'track' == -artist Returns the name of the artist of a music track. -album Returns the name of the album of the music track. +grandparentTitle Returns the title of the item's grandparent. +parentTitle Returns the title of the item's parent. == Only if 'type' is 'episode' or 'movie' == videoDecision Returns the video transcode decision. Either 'transcode', 'copy' or 'direct play'. @@ -83,7 +78,7 @@ DOCUMENTATION :: END % elif a['type'] == 'movie': ${a['title']} % elif a['type'] == 'track': - ${a['artist']} - ${a['track']} + ${a['grandparentTitle']} - ${a['title']} % else: ${a['grandparentTitle']} - ${a['title']} % endif @@ -93,9 +88,9 @@ DOCUMENTATION :: END
% if a['type'] == 'track': - Artist: ${a['artist']} + Artist: ${a['grandparentTitle']}
- Album: ${a['album']} + Album: ${a['parentTitle']}
% endif % if a['state'] == 'playing': diff --git a/data/interfaces/default/js/tables/sync_table.js b/data/interfaces/default/js/tables/sync_table.js index d0c7ba1e..bf2ca84b 100644 --- a/data/interfaces/default/js/tables/sync_table.js +++ b/data/interfaces/default/js/tables/sync_table.js @@ -28,7 +28,8 @@ sync_table_options = { } else { $(td).html(cellData.toProperCase()); } - } + }, + "className": "no-wrap" }, { "targets": [1], @@ -37,7 +38,8 @@ sync_table_options = { if (cellData !== '') { $(td).html('' + cellData + ''); } - } + }, + "className": "no-wrap" }, { "targets": [2], @@ -57,15 +59,18 @@ sync_table_options = { "data": "metadata_type", "render": function ( data, type, full ) { return data.toProperCase(); - } + }, + "className": "no-wrap" }, { "targets": [4], - "data": "device_name" + "data": "device_name", + "className": "no-wrap" }, { "targets": [5], - "data": "platform" + "data": "platform", + "className": "no-wrap" }, { "targets": [6], @@ -77,19 +82,23 @@ sync_table_options = { } else { $(td).html('0MB'); } - } + }, + "className": "no-wrap" }, { "targets": [7], - "data": "item_count" + "data": "item_count", + "className": "no-wrap" }, { "targets": [8], - "data": "item_complete_count" + "data": "item_complete_count", + "className": "no-wrap" }, { "targets": [9], - "data": "item_downloaded_count" + "data": "item_downloaded_count", + "className": "no-wrap" }, { "targets": [10], @@ -100,7 +109,8 @@ sync_table_options = { } else { $(td).html('0%'); } - } + }, + "className": "no-wrap" } ], "drawCallback": function (settings) { diff --git a/data/interfaces/default/sync.html b/data/interfaces/default/sync.html index 51b81249..da6d9da2 100644 --- a/data/interfaces/default/sync.html +++ b/data/interfaces/default/sync.html @@ -33,17 +33,17 @@ from plexpy import helpers - - - - - - - - - - - + + + + + + + + + + + diff --git a/data/interfaces/default/user.html b/data/interfaces/default/user.html index d640eef2..c9f005a2 100644 --- a/data/interfaces/default/user.html +++ b/data/interfaces/default/user.html @@ -222,17 +222,17 @@ from plexpy import helpers
StateUsernameTitleTypeDevicePlatformTotal SizeTotal ItemsConvertedDownloadedCompleteStateUsernameTitleTypeDevicePlatformTotal SizeTotal ItemsConvertedDownloadedComplete
- - - - - - - - - - - + + + + + + + + + + + diff --git a/lib/pynma/pynma.py b/lib/pynma/pynma.py index 037145c8..2bbe3f42 100644 --- a/lib/pynma/pynma.py +++ b/lib/pynma/pynma.py @@ -6,7 +6,7 @@ from urllib import urlencode __version__ = "0.1" -API_SERVER = 'nma.usk.bz' +API_SERVER = 'www.notifymyandroid.com' ADD_PATH = '/publicapi/notify' USER_AGENT="PyNMA/v%s"%__version__ diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 58b3b2c2..5fccea03 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -29,7 +29,7 @@ import uuid from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.interval import IntervalTrigger -from plexpy import versioncheck, logger +from plexpy import versioncheck, logger, monitor import plexpy.config PROG_DIR = None @@ -256,6 +256,9 @@ def initialize_scheduler(): minutes = 0 schedule_job(versioncheck.checkGithub, 'Check GitHub for updates', hours=0, minutes=minutes) + if CONFIG.PMS_IP: + schedule_job(monitor.check_active_sessions, 'Check for active sessions', hours=0, minutes=0, seconds=60) + # Start scheduler if start_jobs and len(SCHED.get_jobs()): try: @@ -267,7 +270,7 @@ def initialize_scheduler(): #SCHED.print_jobs() -def schedule_job(function, name, hours=0, minutes=0): +def schedule_job(function, name, hours=0, minutes=0, seconds=0): """ Start scheduled job if starting or restarting plexpy. Reschedule job if Interval Settings have changed. @@ -277,16 +280,16 @@ def schedule_job(function, name, hours=0, minutes=0): job = SCHED.get_job(name) if job: - if hours == 0 and minutes == 0: + if hours == 0 and minutes == 0 and seconds == 0: SCHED.remove_job(name) logger.info("Removed background task: %s", name) elif job.trigger.interval != datetime.timedelta(hours=hours, minutes=minutes): SCHED.reschedule_job(name, trigger=IntervalTrigger( - hours=hours, minutes=minutes)) + hours=hours, minutes=minutes, seconds=seconds)) logger.info("Re-scheduled background task: %s", name) - elif hours > 0 or minutes > 0: + elif hours > 0 or minutes > 0 or seconds > 0: SCHED.add_job(function, id=name, trigger=IntervalTrigger( - hours=hours, minutes=minutes)) + hours=hours, minutes=minutes, seconds=seconds)) logger.info("Scheduled background task: %s", name) @@ -339,11 +342,23 @@ def dbcheck(): conn.commit() c.close() + conn_db = sqlite3.connect(DB_FILE) + c_db = conn_db.cursor() + c_db.execute( + 'CREATE TABLE IF NOT EXISTS sessions (id INTEGER PRIMARY KEY AUTOINCREMENT, ' + 'session_key INTEGER, rating_key INTEGER, media_type TEXT)' + ) + conn_db.commit() + c_db.close() def shutdown(restart=False, update=False): cherrypy.engine.exit() SCHED.shutdown(wait=False) + # Clear any sessions in the db - Not sure yet if we should do this. More testing required + # logger.debug(u'Clearing Plex sessions.') + # monitor.drop_session_db() + CONFIG.write() if not restart and not update: diff --git a/plexpy/config.py b/plexpy/config.py index fb9854ad..ce8430d8 100644 --- a/plexpy/config.py +++ b/plexpy/config.py @@ -69,10 +69,7 @@ _CONFIG_DEFINITIONS = { 'INTERFACE': (str, 'General', 'default'), 'JOURNAL_MODE': (str, 'Advanced', 'wal'), 'LAUNCH_BROWSER': (int, 'General', 1), - 'LMS_ENABLED': (int, 'LMS', 0), - 'LMS_HOST': (str, 'LMS', ''), 'LOG_DIR': (str, 'General', ''), - 'MPC_ENABLED': (bool_int, 'MPC', False), 'NMA_APIKEY': (str, 'NMA', ''), 'NMA_ENABLED': (int, 'NMA', 0), 'NMA_PRIORITY': (int, 'NMA', 0), @@ -94,11 +91,6 @@ _CONFIG_DEFINITIONS = { 'PUSHOVER_ENABLED': (int, 'Pushover', 0), 'PUSHOVER_KEYS': (str, 'Pushover', ''), 'PUSHOVER_PRIORITY': (int, 'Pushover', 0), - 'SUBSONIC_ENABLED': (int, 'Subsonic', 0), - 'SUBSONIC_HOST': (str, 'Subsonic', ''), - 'SUBSONIC_PASSWORD': (str, 'Subsonic', ''), - 'SUBSONIC_USERNAME': (str, 'Subsonic', ''), - 'SYNOINDEX_ENABLED': (int, 'Synoindex', 0), 'TWITTER_ENABLED': (int, 'Twitter', 0), 'TWITTER_PASSWORD': (str, 'Twitter', ''), 'TWITTER_PREFIX': (str, 'Twitter', 'Headphones'), diff --git a/plexpy/monitor.py b/plexpy/monitor.py new file mode 100644 index 00000000..348bcec9 --- /dev/null +++ b/plexpy/monitor.py @@ -0,0 +1,188 @@ +# This file is part of PlexPy. +# +# PlexPy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# PlexPy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with PlexPy. If not, see . + +from plexpy import logger, helpers, plexwatch, pmsconnect, notification_handler, config + +from xml.dom import minidom +from httplib import HTTPSConnection +from httplib import HTTPConnection + +import os +import sqlite3 +import threading +import plexpy + +monitor_lock = threading.Lock() + +def check_active_sessions(): + + with monitor_lock: + pms_connect = pmsconnect.PmsConnect() + session_list = pms_connect.get_current_activity() + monitor_db = MonitorDatabase() + + if session_list['stream_count'] != '0': + media_container = session_list['sessions'] + active_streams = [] + + for session in media_container: + session_key = session['sessionKey'] + rating_key = session['ratingKey'] + media_type = session['type'] + friendly_name = session['friendly_name'] + platform = session['player'] + title = session['title'] + parent_title = session['parentTitle'] + grandparent_title = session['grandparentTitle'] + + write_session = monitor_db.write_session_key(session_key, rating_key, media_type) + if write_session == 'insert': + # User started playing a stream :: We notify here + if media_type == 'track' or media_type == 'episode': + item_title = grandparent_title + ' - ' + title + elif media_type == 'movie': + item_title = title + else: + item_title = title + logger.info('%s (%s) starting playing %s' % (friendly_name, platform, item_title)) + pushmessage = '%s (%s) starting playing %s' % (friendly_name, platform, item_title) + notification_handler.push_nofitications(pushmessage, 'PlexPy Playback started', 'Playback Started') + + keys = {'session_key': session_key, + 'rating_key': rating_key} + active_streams.append(keys) + + # Check our temp table for what we must do with the new stream + db_streams = monitor_db.select('SELECT session_key, rating_key, media_type FROM sessions') + for result in db_streams: + if any(d['session_key'] == str(result[0]) for d in active_streams): + # The user's session is still active + pass + if any(d['rating_key'] == str(result[1]) for d in active_streams): + # The user is still playing the same media item + # Here we can check the play states + pass + else: + # The user has stopped playing a stream + monitor_db.action('DELETE FROM sessions WHERE session_key = ? AND rating_key = ?', [result[0], result[1]]) + else: + # The user's session is no longer active + monitor_db.action('DELETE FROM sessions WHERE session_key = ?', [result[0]]) + else: + # No sessions exist + # monitor_db.action('DELETE FROM sessions') + pass + +def drop_session_db(): + monitor_db = MonitorDatabase() + monitor_db.action('DROP TABLE sessions') + +def db_filename(filename="plexpy.db"): + + return os.path.join(plexpy.DATA_DIR, filename) + +def get_cache_size(): + # This will protect against typecasting problems produced by empty string and None settings + if not plexpy.CONFIG.CACHE_SIZEMB: + # sqlite will work with this (very slowly) + return 0 + return int(plexpy.CONFIG.CACHE_SIZEMB) + + +class MonitorDatabase(object): + + def __init__(self, filename='plexpy.db'): + self.filename = filename + self.connection = sqlite3.connect(db_filename(filename), timeout=20) + # Don't wait for the disk to finish writing + self.connection.execute("PRAGMA synchronous = OFF") + # Journal disabled since we never do rollbacks + self.connection.execute("PRAGMA journal_mode = %s" % plexpy.CONFIG.JOURNAL_MODE) + # 64mb of cache memory, probably need to make it user configurable + self.connection.execute("PRAGMA cache_size=-%s" % (get_cache_size() * 1024)) + self.connection.row_factory = sqlite3.Row + + def action(self, query, args=None): + + if query is None: + return + + sql_result = None + + try: + with self.connection as c: + if args is None: + sql_result = c.execute(query) + else: + sql_result = c.execute(query, args) + + except sqlite3.OperationalError, e: + if "unable to open database file" in e.message or "database is locked" in e.message: + logger.warn('Database Error: %s', e) + else: + logger.error('Database error: %s', e) + raise + + except sqlite3.DatabaseError, e: + logger.error('Fatal Error executing %s :: %s', query, e) + raise + + return sql_result + + def write_session_key(self, session_key=None, rating_key=None, media_type=None): + + values = {'rating_key': rating_key, + 'media_type': media_type} + + keys = {'session_key': session_key, + 'rating_key': rating_key} + + result = self.upsert('sessions', values, keys) + return result + + def select(self, query, args=None): + + sql_results = self.action(query, args).fetchall() + + if sql_results is None or sql_results == [None]: + return [] + + return sql_results + + def upsert(self, table_name, value_dict, key_dict): + + trans_type = 'update' + changes_before = self.connection.total_changes + + gen_params = lambda my_dict: [x + " = ?" for x in my_dict.keys()] + + update_query = "UPDATE " + table_name + " SET " + ", ".join(gen_params(value_dict)) + \ + " WHERE " + " AND ".join(gen_params(key_dict)) + + self.action(update_query, value_dict.values() + key_dict.values()) + + if self.connection.total_changes == changes_before: + trans_type = 'insert' + insert_query = ( + "INSERT INTO " + table_name + " (" + ", ".join(value_dict.keys() + key_dict.keys()) + ")" + + " VALUES (" + ", ".join(["?"] * len(value_dict.keys() + key_dict.keys())) + ")" + ) + try: + self.action(insert_query, value_dict.values() + key_dict.values()) + except sqlite3.IntegrityError: + logger.info('Queries failed: %s and %s', update_query, insert_query) + + # We want to know if it was an update or insert + return trans_type diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py new file mode 100644 index 00000000..f1901699 --- /dev/null +++ b/plexpy/notification_handler.py @@ -0,0 +1,90 @@ +# This file is part of PlexPy. +# +# PlexPy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# PlexPy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with PlexPy. If not, see . + +from plexpy import logger, config, notifiers + +import plexpy + + +def push_nofitications(push_message=None, subject=None, status_message=None): + + if push_message: + if not subject: + subject = 'PlexPy' + + if plexpy.CONFIG.GROWL_ENABLED: + logger.info(u"Growl request") + growl = notifiers.GROWL() + growl.notify(push_message, status_message) + + if plexpy.CONFIG.PROWL_ENABLED: + logger.info(u"Prowl request") + prowl = notifiers.PROWL() + prowl.notify(push_message, status_message) + + if plexpy.CONFIG.XBMC_ENABLED: + xbmc = notifiers.XBMC() + if plexpy.CONFIG.XBMC_NOTIFY: + xbmc.notify(subject, push_message) + + if plexpy.CONFIG.PLEX_ENABLED: + plex = notifiers.Plex() + if plexpy.CONFIG.PLEX_NOTIFY: + plex.notify(subject, push_message) + + if plexpy.CONFIG.NMA_ENABLED: + nma = notifiers.NMA() + nma.notify(subject, push_message) + + if plexpy.CONFIG.PUSHALOT_ENABLED: + logger.info(u"Pushalot request") + pushalot = notifiers.PUSHALOT() + pushalot.notify(push_message, status_message) + + if plexpy.CONFIG.PUSHOVER_ENABLED: + logger.info(u"Pushover request") + pushover = notifiers.PUSHOVER() + pushover.notify(push_message, status_message) + + if plexpy.CONFIG.PUSHBULLET_ENABLED: + logger.info(u"PushBullet request") + pushbullet = notifiers.PUSHBULLET() + pushbullet.notify(push_message, status_message) + + if plexpy.CONFIG.TWITTER_ENABLED: + logger.info(u"Sending Twitter notification") + twitter = notifiers.TwitterNotifier() + twitter.notify_download(push_message) + + if plexpy.CONFIG.OSX_NOTIFY_ENABLED: + # TODO: Get thumb in notification + # from plexpy import cache + # c = cache.Cache() + # album_art = c.get_artwork_from_cache(None, release['AlbumID']) + logger.info(u"Sending OS X notification") + osx_notify = notifiers.OSX_NOTIFY() + osx_notify.notify(subject, push_message) + + if plexpy.CONFIG.BOXCAR_ENABLED: + logger.info(u"Sending Boxcar2 notification") + boxcar = notifiers.BOXCAR() + boxcar.notify(subject, push_message) + + if plexpy.CONFIG.EMAIL_ENABLED: + logger.info(u"Sending Email notification") + email = notifiers.Email() + email.notify(subject=subject, message=push_message) + else: + logger.warning('Notification requested but no message received.') \ No newline at end of file diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index 2edfaf8f..f375fc20 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -178,20 +178,6 @@ class PROWL(object): self.notify('ZOMG Lazors Pewpewpew!', 'Test Message') - -class MPC(object): - """ - MPC library update - """ - - def __init__(self): - - pass - - def notify(self): - subprocess.call(["mpc", "update"]) - - class XBMC(object): """ XBMC notifications @@ -225,25 +211,12 @@ class XBMC(object): if response: return response[0]['result'] - def update(self): - # From what I read you can't update the music library on a per directory or per path basis - # so need to update the whole thing + def notify(self, subject=None, message=None): hosts = [x.strip() for x in self.hosts.split(',')] - for host in hosts: - logger.info('Sending library update command to XBMC @ ' + host) - request = self._sendjson(host, 'AudioLibrary.Scan') - - if not request: - logger.warn('Error sending update request to XBMC') - - def notify(self, artist, album, albumartpath): - - hosts = [x.strip() for x in self.hosts.split(',')] - - header = "PlexPy" - message = "%s - %s added to your library" % (artist, album) + header = subject + message = message time = "3000" # in ms for host in hosts: @@ -252,12 +225,12 @@ class XBMC(object): version = self._sendjson(host, 'Application.GetProperties', {'properties': ['version']})['version']['major'] if version < 12: #Eden - notification = header + "," + message + "," + time + "," + albumartpath + notification = header + "," + message + "," + time notifycommand = {'command': 'ExecBuiltIn', 'parameter': 'Notification(' + notification + ')'} request = self._sendhttp(host, notifycommand) else: #Frodo - params = {'title': header, 'message': message, 'displaytime': int(time), 'image': albumartpath} + params = {'title': header, 'message': message, 'displaytime': int(time)} request = self._sendjson(host, 'GUI.ShowNotification', params) if not request: @@ -267,48 +240,6 @@ class XBMC(object): logger.error('Error sending notification request to XBMC') -class LMS(object): - """ - Class for updating a Logitech Media Server - """ - - def __init__(self): - self.hosts = plexpy.CONFIG.LMS_HOST - - def _sendjson(self, host): - data = {'id': 1, 'method': 'slim.request', 'params': ["", ["rescan"]]} - data = json.JSONEncoder().encode(data) - - content = {'Content-Type': 'application/json'} - - req = urllib2.Request(host + '/jsonrpc.js', data, content) - - try: - handle = urllib2.urlopen(req) - except Exception as e: - logger.warn('Error opening LMS url: %s' % e) - return - - response = json.JSONDecoder().decode(handle.read()) - - try: - return response['result'] - except: - logger.warn('LMS returned error: %s' % response['error']) - return response['error'] - - def update(self): - - hosts = [x.strip() for x in self.hosts.split(',')] - - for host in hosts: - logger.info('Sending library rescan command to LMS @ ' + host) - request = self._sendjson(host) - - if request: - logger.warn('Error sending rescan request to LMS') - - class Plex(object): def __init__(self): @@ -344,48 +275,18 @@ class Plex(object): return response - def update(self): - - # From what I read you can't update the music library on a per directory or per path basis - # so need to update the whole thing - - hosts = [x.strip() for x in self.server_hosts.split(',')] - - for host in hosts: - logger.info('Sending library update command to Plex Media Server@ ' + host) - url = "%s/library/sections" % host - try: - xml_sections = minidom.parse(urllib.urlopen(url)) - except IOError, e: - logger.warn("Error while trying to contact Plex Media Server: %s" % e) - return False - - sections = xml_sections.getElementsByTagName('Directory') - if not sections: - logger.info(u"Plex Media Server not running on: " + host) - return False - - for s in sections: - if s.getAttribute('type') == "artist": - url = "%s/library/sections/%s/refresh" % (host, s.getAttribute('key')) - try: - urllib.urlopen(url) - except Exception as e: - logger.warn("Error updating library section for Plex Media Server: %s" % e) - return False - - def notify(self, artist, album, albumartpath): + def notify(self, subject=None, message=None): hosts = [x.strip() for x in self.client_hosts.split(',')] - header = "PlexPy" - message = "%s - %s added to your library" % (artist, album) + header = subject + message = message time = "3000" # in ms for host in hosts: logger.info('Sending notification command to Plex Media Server @ ' + host) try: - notification = header + "," + message + "," + time + "," + albumartpath + notification = header + "," + message + "," + time notifycommand = {'command': 'ExecBuiltIn', 'parameter': 'Notification(' + notification + ')'} request = self._sendhttp(host, notifycommand) @@ -397,7 +298,7 @@ class Plex(object): class NMA(object): - def notify(self, artist=None, album=None, snatched=None): + def notify(self, subject=None, message=None): title = 'PlexPy' api = plexpy.CONFIG.NMA_APIKEY nma_priority = plexpy.CONFIG.NMA_PRIORITY @@ -406,12 +307,7 @@ class NMA(object): logger.debug(u"NMA API: " + api) logger.debug(u"NMA Priority: " + str(nma_priority)) - if snatched: - event = snatched + " snatched!" - message = "PlexPy has snatched: " + snatched - else: - event = artist + ' - ' + album + ' complete!' - message = "PlexPy has downloaded and postprocessed: " + artist + ' [' + album + ']' + event = subject logger.debug(u"NMA event: " + event) logger.debug(u"NMA message: " + message) @@ -460,9 +356,9 @@ class PUSHBULLET(object): body=json.dumps(data)) response = http_handler.getresponse() request_status = response.status - logger.debug(u"PushBullet response status: %r" % request_status) - logger.debug(u"PushBullet response headers: %r" % response.getheaders()) - logger.debug(u"PushBullet response body: %r" % response.read()) + # logger.debug(u"PushBullet response status: %r" % request_status) + # logger.debug(u"PushBullet response headers: %r" % response.getheaders()) + # logger.debug(u"PushBullet response body: %r" % response.read()) if request_status == 200: logger.info(u"PushBullet notifications sent.") @@ -474,10 +370,6 @@ class PUSHBULLET(object): logger.info(u"PushBullet notification failed serverside.") return False - def updateLibrary(self): - #For uniformity reasons not removed - return - def test(self, apikey, deviceid): self.enabled = True @@ -527,43 +419,6 @@ class PUSHALOT(object): return False -class Synoindex(object): - def __init__(self, util_loc='/usr/syno/bin/synoindex'): - self.util_loc = util_loc - - def util_exists(self): - return os.path.exists(self.util_loc) - - def notify(self, path): - path = os.path.abspath(path) - - if not self.util_exists(): - logger.warn("Error sending notification: synoindex utility not found at %s" % self.util_loc) - return - - if os.path.isfile(path): - cmd_arg = '-a' - elif os.path.isdir(path): - cmd_arg = '-A' - else: - logger.warn("Error sending notification: Path passed to synoindex was not a file or folder.") - return - - cmd = [self.util_loc, cmd_arg, path] - logger.info("Calling synoindex command: %s" % str(cmd)) - try: - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=plexpy.PROG_DIR) - out, error = p.communicate() - #synoindex never returns any codes other than '0', highly irritating - except OSError, e: - logger.warn("Error sending notification: %s" % str(e)) - - def notify_multiple(self, path_list): - if isinstance(path_list, list): - for path in path_list: - self.notify(path) - - class PUSHOVER(object): def __init__(self): @@ -814,25 +669,6 @@ class BOXCAR(object): return False -class SubSonicNotifier(object): - - def __init__(self): - self.host = plexpy.CONFIG.SUBSONIC_HOST - self.username = plexpy.CONFIG.SUBSONIC_USERNAME - self.password = plexpy.CONFIG.SUBSONIC_PASSWORD - - def notify(self, albumpaths): - # Correct URL - if not self.host.lower().startswith("http"): - self.host = "http://" + self.host - - if not self.host.lower().endswith("/"): - self.host = self.host + "/" - - # Invoke request - request.request_response(self.host + "musicFolderSettings.view?scanNow", - auth=(self.username, self.password)) - class Email(object): def notify(self, subject, message): diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index f994ffd3..31b7fae4 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -581,6 +581,7 @@ class PmsConnect(object): progress = self.get_xml_attr(session, 'viewOffset') session_output = {'sessionKey': self.get_xml_attr(session, 'sessionKey'), + 'art': self.get_xml_attr(session, 'art'), 'parentThumb': self.get_xml_attr(session, 'parentThumb'), 'thumb': self.get_xml_attr(session, 'thumb'), 'user': self.get_xml_attr(session.getElementsByTagName('User')[0], 'title'), @@ -588,13 +589,17 @@ class PmsConnect(object): self.get_xml_attr(session.getElementsByTagName('User')[0], 'title')), 'player': self.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'state': self.get_xml_attr(session.getElementsByTagName('Player')[0], 'state'), - 'artist': self.get_xml_attr(session, 'grandparentTitle'), - 'album': self.get_xml_attr(session, 'parentTitle'), - 'track': self.get_xml_attr(session, 'title'), + 'grandparentTitle': self.get_xml_attr(session, 'grandparentTitle'), + 'parentTitle': self.get_xml_attr(session, 'parentTitle'), + 'title': self.get_xml_attr(session, 'title'), 'ratingKey': self.get_xml_attr(session, 'ratingKey'), 'audioDecision': audio_decision, 'audioChannels': audio_channels, 'audioCodec': audio_codec, + 'videoDecision': '', + 'videoCodec': '', + 'height': '', + 'width': '', 'duration': duration, 'progress': progress, 'progressPercent': str(helpers.get_percent(progress, duration)), @@ -647,6 +652,7 @@ class PmsConnect(object): if self.get_xml_attr(session, 'type') == 'episode': session_output = {'sessionKey': self.get_xml_attr(session, 'sessionKey'), 'art': self.get_xml_attr(session, 'art'), + 'parentThumb': self.get_xml_attr(session, 'parentThumb'), 'thumb': thumb, 'user': self.get_xml_attr(session.getElementsByTagName('User')[0], 'title'), 'friendly_name': plex_watch.get_user_friendly_name( @@ -654,6 +660,7 @@ class PmsConnect(object): 'player': self.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'state': self.get_xml_attr(session.getElementsByTagName('Player')[0], 'state'), 'grandparentTitle': self.get_xml_attr(session, 'grandparentTitle'), + 'parentTitle': self.get_xml_attr(session, 'parentTitle'), 'title': self.get_xml_attr(session, 'title'), 'ratingKey': self.get_xml_attr(session, 'ratingKey'), 'audioDecision': audio_decision, @@ -673,11 +680,14 @@ class PmsConnect(object): session_output = {'sessionKey': self.get_xml_attr(session, 'sessionKey'), 'art': self.get_xml_attr(session, 'art'), 'thumb': thumb, + 'parentThumb': self.get_xml_attr(session, 'parentThumb'), 'user': self.get_xml_attr(session.getElementsByTagName('User')[0], 'title'), 'friendly_name': plex_watch.get_user_friendly_name( self.get_xml_attr(session.getElementsByTagName('User')[0], 'title')), 'player': self.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'state': self.get_xml_attr(session.getElementsByTagName('Player')[0], 'state'), + 'grandparentTitle': self.get_xml_attr(session, 'grandparentTitle'), + 'parentTitle': self.get_xml_attr(session, 'parentTitle'), 'title': self.get_xml_attr(session, 'title'), 'ratingKey': self.get_xml_attr(session, 'ratingKey'), 'audioDecision': audio_decision, diff --git a/plexpy/webserve.py b/plexpy/webserve.py index bc39c42c..a6c39904 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -274,8 +274,6 @@ class WebInterface(object): "xbmc_host": plexpy.CONFIG.XBMC_HOST, "xbmc_username": plexpy.CONFIG.XBMC_USERNAME, "xbmc_password": plexpy.CONFIG.XBMC_PASSWORD, - "lms_enabled": checked(plexpy.CONFIG.LMS_ENABLED), - "lms_host": plexpy.CONFIG.LMS_HOST, "plex_enabled": checked(plexpy.CONFIG.PLEX_ENABLED), "plex_client_host": plexpy.CONFIG.PLEX_CLIENT_HOST, "plex_username": plexpy.CONFIG.PLEX_USERNAME, @@ -285,7 +283,6 @@ class WebInterface(object): "nma_priority": int(plexpy.CONFIG.NMA_PRIORITY), "pushalot_enabled": checked(plexpy.CONFIG.PUSHALOT_ENABLED), "pushalot_apikey": plexpy.CONFIG.PUSHALOT_APIKEY, - "synoindex_enabled": checked(plexpy.CONFIG.SYNOINDEX_ENABLED), "pushover_enabled": checked(plexpy.CONFIG.PUSHOVER_ENABLED), "pushover_keys": plexpy.CONFIG.PUSHOVER_KEYS, "pushover_apitoken": plexpy.CONFIG.PUSHOVER_APITOKEN, @@ -293,17 +290,12 @@ class WebInterface(object): "pushbullet_enabled": checked(plexpy.CONFIG.PUSHBULLET_ENABLED), "pushbullet_apikey": plexpy.CONFIG.PUSHBULLET_APIKEY, "pushbullet_deviceid": plexpy.CONFIG.PUSHBULLET_DEVICEID, - "subsonic_enabled": checked(plexpy.CONFIG.SUBSONIC_ENABLED), - "subsonic_host": plexpy.CONFIG.SUBSONIC_HOST, - "subsonic_username": plexpy.CONFIG.SUBSONIC_USERNAME, - "subsonic_password": plexpy.CONFIG.SUBSONIC_PASSWORD, "twitter_enabled": checked(plexpy.CONFIG.TWITTER_ENABLED), "osx_notify_enabled": checked(plexpy.CONFIG.OSX_NOTIFY_ENABLED), "osx_notify_app": plexpy.CONFIG.OSX_NOTIFY_APP, "boxcar_enabled": checked(plexpy.CONFIG.BOXCAR_ENABLED), "boxcar_token": plexpy.CONFIG.BOXCAR_TOKEN, "cache_sizemb": plexpy.CONFIG.CACHE_SIZEMB, - "mpc_enabled": checked(plexpy.CONFIG.MPC_ENABLED), "email_enabled": checked(plexpy.CONFIG.EMAIL_ENABLED), "email_from": plexpy.CONFIG.EMAIL_FROM, "email_to": plexpy.CONFIG.EMAIL_TO, @@ -333,11 +325,11 @@ class WebInterface(object): checked_configs = [ "launch_browser", "enable_https", "api_enabled", "freeze_db", "growl_enabled", - "prowl_enabled", "xbmc_enabled", "lms_enabled", + "prowl_enabled", "xbmc_enabled", "plex_enabled", "nma_enabled", "pushalot_enabled", - "synoindex_enabled", "pushover_enabled", "pushbullet_enabled", - "subsonic_enabled", "twitter_enabled", "osx_notify_enabled", - "boxcar_enabled", "mpc_enabled", "email_enabled", "email_tls", + "pushover_enabled", "pushbullet_enabled", + "twitter_enabled", "osx_notify_enabled", + "boxcar_enabled", "email_enabled", "email_tls", "grouping_global_history", "grouping_user_history", "grouping_charts", "pms_use_bif" ] for checked_config in checked_configs: @@ -873,6 +865,18 @@ class WebInterface(object): else: logger.warn('Unable to retrieve data.') + @cherrypy.expose + def get_activity(self, **kwargs): + + pms_connect = pmsconnect.PmsConnect() + result = pms_connect.get_current_activity() + + if result: + cherrypy.response.headers['Content-type'] = 'application/json' + return json.dumps(result) + else: + logger.warn('Unable to retrieve data.') + @cherrypy.expose def get_full_users_list(self, **kwargs):
StateUsernameTitleTypeDevicePlatformTotal SizeTotal ItemsConvertedDownloadedCompleteStateUsernameTitleTypeDevicePlatformTotal SizeTotal ItemsConvertedDownloadedComplete