We're getting our own database!

First code for independent notifications (not linked to PlexWatch).
New notifications panel in Settings (many types still untested).
Standardise the parameters sent to current activity.
Remove notifiers we cannot use.
Styling fixes for sync tables.
This commit is contained in:
Tim 2015-07-03 19:50:47 +02:00
parent b244e09c24
commit 349a850451
14 changed files with 401 additions and 354 deletions

View file

@ -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)

View file

@ -40,7 +40,7 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation" class="active"><a href="#tabs-1" aria-controls="tabs-1" role="tab" data-toggle="tab">Web Interface</a></li>
<li role="presentation"><a href="#tabs-2" aria-controls="tabs-2" role="tab" data-toggle="tab">Plex & PlexWatch</a></li>
<!--<li role="presentation"><a href="#tabs-5" aria-controls="tabs-5" role="tab" data-toggle="tab">Notifications</a></li>-->
<li role="presentation"><a href="#tabs-5" aria-controls="tabs-5" role="tab" data-toggle="tab">Notifications</a></li>
</ul>
<div class="tab-content">
@ -206,13 +206,13 @@
</div>
</div>
<input type="button" class="btn btn-medium btn-primary save-button" value="Save" data-success="Changes saved successfully">
</div>
<!--
<div role="tabpanel" class="tab-pane" id="tabs-5">
<div class="wellbg">
<div class="wellbg" style="padding: 0px 0px 0px 20px;">
<div class="container-fluid">
<div class="row">
<div class="col-md-4">
<div class="row-fluid">
<div class="span4">
<fieldset>
<div class="wellheader">
<h3>Growl</h3>
@ -298,17 +298,9 @@
</div>
</div>
</fieldset>
<fieldset>
<div class="wellheader">
<h3>Synology NAS</h3>
</div>
<div class="checkbox">
<input type="checkbox" name="synoindex_enabled" id="synoindex" value="1" ${config['synoindex_enabled']}> Enable Synoindex
</div>
</fieldset>
</div>
<div class="col-md-4">
<div class="span4">
<fieldset>
<div class="wellheader">
<h3>NotifyMyAndroid</h3>
@ -348,21 +340,6 @@
</div>
</div>
</fieldset>
<fieldset>
<div class="wellheader">
<h3>Logitech Media Server</h3>
</div>
<div class="checkbox">
<input type="checkbox" name="lms_enabled" id="lms" value="1" ${config['lms_enabled']}> Enable LMS Updates
</div>
<div id="lmsoptions">
<div class="form-group">
<label for="lms_host">LMS Host:Port</label>
<input type="text" id="lms_host" name="lms_host" value="${config['lms_host']}" size="30">
<p class="help-block">e.g. http://localhost:9000. Seperate hosts with commas.</p>
</div>
</div>
</fieldset>
<fieldset>
<div class="wellheader">
@ -380,28 +357,6 @@
</div>
</fieldset>
<fieldset>
<div class="wellheader">
<h3>Subsonic</h3>
</div>
<div class="checkbox">
<input type="checkbox" name="subsonic_enabled" id="subsonic" value="1" ${config['subsonic_enabled']}> Enable Subsonic Updates
</div>
<div id="subsonicoptions">
<div class="form-group">
<label for="subsonic_host">Subsonic URL</label>
<input type="text" id="subsonic_host" name="subsonic_host" value="${config['subsonic_host']}" size="30">
</div>
<div class="form-group">
<label for="subsonic_username">Subsonic Username</label>
<input type="text" id="subsonic_username" name="subsonic_username" value="${config['subsonic_username']}" size="30">
</div>
<div class="form-group">
<label for="subsonic_password">Subsonic Password</label>
<input type="password" id="subsonic_password" name="subsonic_password" value="${config['subsonic_password']}" size="30">
</div>
</div>
</fieldset>
<fieldset>
<div class="wellheader">
<h3>Email</h3>
@ -441,7 +396,7 @@
</fieldset>
</div>
<div class="md-col-4">
<div class="span4">
<fieldset>
<div class="wellheader">
<h3>Pushover</h3>
@ -484,6 +439,7 @@
</div>
</fieldset>
<!-- We need to test Twitter first
<fieldset>
<div class="wellheader">
<h3>Twitter</h3>
@ -504,7 +460,7 @@
</div>
</div>
</fieldset>
-->
<fieldset>
<div class="wellheader">
<h3>OS X</h3>
@ -537,22 +493,13 @@
</div>
</fieldset>
<fieldset>
<div class="wellheader">
<h3>MPC</h3>
</div>
<div class="checkbox">
<input type="checkbox" name="mpc_enabled" id="mpc" value="1" ${config['mpc_enabled']}> Enable MPC Update
</div>
</fieldset>
</div>
</div>
</div>
<br/>
</div>
<input type="button" class="btn btn-medium btn-primary" value="Save" onclick="doAjaxCall('configUpdate',$(this),'tabs',true);return false;" data-success="Changes saved successfully">
<input type="button" class="btn btn-medium btn-primary save-button" value="Save" data-success="Changes saved successfully">
</div>
-->
</div>
</div>
</form>
@ -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();

View file

@ -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 href="info?rating_key=${a['ratingKey']}">${a['title']}</a>
% elif a['type'] == 'track':
${a['artist']} - ${a['track']}
${a['grandparentTitle']} - ${a['title']}
% else:
${a['grandparentTitle']} - ${a['title']}
% endif
@ -93,9 +88,9 @@ DOCUMENTATION :: END
<div class='dashboard-activity-info-details-overlay'>
<div class='dashboard-activity-info-details-content'>
% if a['type'] == 'track':
Artist: <strong>${a['artist']}</strong>
Artist: <strong>${a['grandparentTitle']}</strong>
<br>
Album: <strong>${a['album']}</strong>
Album: <strong>${a['parentTitle']}</strong>
<br>
% endif
% if a['state'] == 'playing':

View file

@ -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('<a href="user?user=' + rowData['username'] + '">' + cellData + '</a>');
}
}
},
"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('<span class="badge">0%</span>');
}
}
},
"className": "no-wrap"
}
],
"drawCallback": function (settings) {

View file

@ -33,17 +33,17 @@ from plexpy import helpers
<table class="display" id="sync_table" width="100%">
<thead>
<tr>
<th align='left' id="state">State</th>
<th align='left' id="username">Username</th>
<th align='left' id="title">Title</th>
<th align='left' id="type">Type</th>
<th align='left' id="device">Device</th>
<th align='left' id="platform">Platform</th>
<th align='left' id="size">Total Size</th>
<th align='left' id="items">Total Items</th>
<th align='left' id="converted">Converted</th>
<th align='left' id="downloaded">Downloaded</th>
<th align='left' id="percent_complete">Complete</th>
<th class="desktop" align='left' id="state">State</th>
<th class="all" align='left' id="username">Username</th>
<th class="all" align='left' id="title">Title</th>
<th class="desktop" align='left' id="type">Type</th>
<th class="min-tablet" align='left' id="device">Device</th>
<th class="desktop" align='left' id="platform">Platform</th>
<th class="desktop" align='left' id="size">Total Size</th>
<th class="min-tablet" align='left' id="items">Total Items</th>
<th class="desktop" align='left' id="converted">Converted</th>
<th class="desktop" align='left' id="downloaded">Downloaded</th>
<th class="desktop" align='left' id="percent_complete">Complete</th>
</tr>
</thead>
<tbody>

View file

@ -222,17 +222,17 @@ from plexpy import helpers
<table class="display" id="sync_table" width="100%">
<thead>
<tr>
<th align='left' id="state">State</th>
<th align='left' id="username">Username</th>
<th align='left' id="sync_title">Title</th>
<th align='left' id="type">Type</th>
<th align='left' id="device">Device</th>
<th align='left' id="sync_platform">Platform</th>
<th align='left' id="size">Total Size</th>
<th align='left' id="items">Total Items</th>
<th align='left' id="converted">Converted</th>
<th align='left' id="downloaded">Downloaded</th>
<th align='left' id="sync_percent_complete">Complete</th>
<th class="desktop" align='left' id="state">State</th>
<th class="never" align='left' id="username">Username</th>
<th class="all" align='left' id="sync_title">Title</th>
<th class="desktop" align='left' id="type">Type</th>
<th class="all" align='left' id="device">Device</th>
<th class="desktop" align='left' id="sync_platform">Platform</th>
<th class="desktop" align='left' id="size">Total Size</th>
<th class="min-tablet" align='left' id="items">Total Items</th>
<th class="desktop" align='left' id="converted">Converted</th>
<th class="desktop" align='left' id="downloaded">Downloaded</th>
<th class="desktop" align='left' id="sync_percent_complete">Complete</th>
</tr>
</thead>
<tbody>

View file

@ -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__

View file

@ -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:

View file

@ -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'),

188
plexpy/monitor.py Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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

View file

@ -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 <http://www.gnu.org/licenses/>.
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.')

View file

@ -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):

View file

@ -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,

View file

@ -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):