plexpy/plexpy/webserve.py
2015-06-21 11:21:23 +02:00

620 lines
23 KiB
Python

# 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, notifiers, plextv, pmsconnect, plexwatch
from plexpy.helpers import checked, radio, today, cleanName
from xml.dom import minidom
from mako.lookup import TemplateLookup
from mako import exceptions
from operator import itemgetter
import plexpy
import threading
import cherrypy
import urllib2
import hashlib
import random
import urllib
import json
import time
import sys
import os
try:
# pylint:disable=E0611
# ignore this error because we are catching the ImportError
from collections import OrderedDict
# pylint:enable=E0611
except ImportError:
# Python 2.6.x fallback, from libs
from ordereddict import OrderedDict
def serve_template(templatename, **kwargs):
interface_dir = os.path.join(str(plexpy.PROG_DIR), 'data/interfaces/')
template_dir = os.path.join(str(interface_dir), plexpy.CONFIG.INTERFACE)
_hplookup = TemplateLookup(directories=[template_dir])
try:
template = _hplookup.get_template(templatename)
return template.render(**kwargs)
except:
return exceptions.html_error_template().render()
class WebInterface(object):
@cherrypy.expose
def index(self):
raise cherrypy.HTTPRedirect("home")
@cherrypy.expose
def home(self):
return serve_template(templatename="index.html", title="Home")
@cherrypy.expose
def get_date_formats(self):
if plexpy.CONFIG.DATE_FORMAT:
date_format = plexpy.CONFIG.DATE_FORMAT
else:
date_format = 'YYYY-MM-DD'
if plexpy.CONFIG.TIME_FORMAT:
time_format = plexpy.CONFIG.TIME_FORMAT
else:
time_format = 'HH:mm'
formats = {'date_format': date_format,
'time_format': time_format}
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps(formats)
@cherrypy.expose
def history(self):
return serve_template(templatename="history.html", title="History")
@cherrypy.expose
def users(self):
return serve_template(templatename="users.html", title="Users")
@cherrypy.expose
def user(self, user=None):
return serve_template(templatename="user.html", title="User", user=user)
@cherrypy.expose
def get_stream_data(self, row_id=None, user=None, **kwargs):
plex_watch = plexwatch.PlexWatch()
stream_data = plex_watch.get_stream_details(row_id)
return serve_template(templatename="stream_data.html", title="Stream Data", data=stream_data, user=user)
@cherrypy.expose
def get_user_list(self, start=0, length=100, **kwargs):
plex_watch = plexwatch.PlexWatch()
users = plex_watch.get_user_list(start, length, kwargs)
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps(users)
@cherrypy.expose
def checkGithub(self):
from plexpy import versioncheck
versioncheck.checkGithub()
raise cherrypy.HTTPRedirect("home")
@cherrypy.expose
def logs(self):
return serve_template(templatename="logs.html", title="Log", lineList=plexpy.LOG_LIST)
@cherrypy.expose
def clearLogs(self):
plexpy.LOG_LIST = []
logger.info("Web logs cleared")
raise cherrypy.HTTPRedirect("logs")
@cherrypy.expose
def toggleVerbose(self):
plexpy.VERBOSE = not plexpy.VERBOSE
logger.initLogger(console=not plexpy.QUIET,
log_dir=plexpy.CONFIG.LOG_DIR, verbose=plexpy.VERBOSE)
logger.info("Verbose toggled, set to %s", plexpy.VERBOSE)
logger.debug("If you read this message, debug logging is available")
raise cherrypy.HTTPRedirect("logs")
@cherrypy.expose
def getLog(self, start=0, length=100, **kwargs):
start = int(start)
length = int(length)
search_value = ""
search_regex = ""
order_column = 0
order_dir = "desc"
if 'order[0][dir]' in kwargs:
order_dir = kwargs.get('order[0][dir]', "desc")
if 'order[0][column]' in kwargs:
order_column = kwargs.get('order[0][column]', "0")
if 'search[value]' in kwargs:
search_value = kwargs.get('search[value]', "")
if 'search[regex]' in kwargs:
search_regex = kwargs.get('search[regex]', "")
filtered = []
if search_value == "":
filtered = plexpy.LOG_LIST[::]
else:
filtered = [row for row in plexpy.LOG_LIST for column in row if search_value.lower() in column.lower()]
sortcolumn = 0
if order_column == '1':
sortcolumn = 2
elif order_column == '2':
sortcolumn = 1
filtered.sort(key=lambda x: x[sortcolumn], reverse=order_dir == "desc")
rows = filtered[start:(start + length)]
rows = [[row[0], row[2], row[1]] for row in rows]
return json.dumps({
'recordsFiltered': len(filtered),
'recordsTotal': len(plexpy.LOG_LIST),
'data': rows,
})
@cherrypy.expose
def generateAPI(self):
apikey = hashlib.sha224(str(random.getrandbits(256))).hexdigest()[0:32]
logger.info("New API generated")
return apikey
@cherrypy.expose
def config(self):
interface_dir = os.path.join(plexpy.PROG_DIR, 'data/interfaces/')
interface_list = [name for name in os.listdir(interface_dir) if
os.path.isdir(os.path.join(interface_dir, name))]
# Initialise blank passwords so we do not expose them in the html forms
# but users are still able to clear them
if plexpy.CONFIG.HTTP_PASSWORD != '':
http_password = ' '
else:
http_password = ''
if plexpy.CONFIG.PMS_PASSWORD != '':
pms_password = ' '
else:
pms_password = ''
config = {
"http_host": plexpy.CONFIG.HTTP_HOST,
"http_username": plexpy.CONFIG.HTTP_USERNAME,
"http_port": plexpy.CONFIG.HTTP_PORT,
"http_password": http_password,
"launch_browser": checked(plexpy.CONFIG.LAUNCH_BROWSER),
"enable_https": checked(plexpy.CONFIG.ENABLE_HTTPS),
"https_cert": plexpy.CONFIG.HTTPS_CERT,
"https_key": plexpy.CONFIG.HTTPS_KEY,
"api_enabled": checked(plexpy.CONFIG.API_ENABLED),
"api_key": plexpy.CONFIG.API_KEY,
"update_db_interval": plexpy.CONFIG.UPDATE_DB_INTERVAL,
"freeze_db": checked(plexpy.CONFIG.FREEZE_DB),
"log_dir": plexpy.CONFIG.LOG_DIR,
"cache_dir": plexpy.CONFIG.CACHE_DIR,
"interface_list": interface_list,
"growl_enabled": checked(plexpy.CONFIG.GROWL_ENABLED),
"growl_host": plexpy.CONFIG.GROWL_HOST,
"growl_password": plexpy.CONFIG.GROWL_PASSWORD,
"prowl_enabled": checked(plexpy.CONFIG.PROWL_ENABLED),
"prowl_keys": plexpy.CONFIG.PROWL_KEYS,
"prowl_priority": plexpy.CONFIG.PROWL_PRIORITY,
"xbmc_enabled": checked(plexpy.CONFIG.XBMC_ENABLED),
"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,
"plex_password": plexpy.CONFIG.PLEX_PASSWORD,
"nma_enabled": checked(plexpy.CONFIG.NMA_ENABLED),
"nma_apikey": plexpy.CONFIG.NMA_APIKEY,
"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,
"pushover_priority": plexpy.CONFIG.PUSHOVER_PRIORITY,
"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,
"email_smtp_server": plexpy.CONFIG.EMAIL_SMTP_SERVER,
"email_smtp_user": plexpy.CONFIG.EMAIL_SMTP_USER,
"email_smtp_password": plexpy.CONFIG.EMAIL_SMTP_PASSWORD,
"email_smtp_port": int(plexpy.CONFIG.EMAIL_SMTP_PORT),
"email_tls": checked(plexpy.CONFIG.EMAIL_TLS),
"pms_ip": plexpy.CONFIG.PMS_IP,
"pms_port": plexpy.CONFIG.PMS_PORT,
"pms_username": plexpy.CONFIG.PMS_USERNAME,
"pms_password": pms_password,
"plexwatch_database": plexpy.CONFIG.PLEXWATCH_DATABASE,
"date_format": plexpy.CONFIG.DATE_FORMAT,
"time_format": plexpy.CONFIG.TIME_FORMAT,
"grouping_global_history": checked(plexpy.CONFIG.GROUPING_GLOBAL_HISTORY),
"grouping_user_history": checked(plexpy.CONFIG.GROUPING_USER_HISTORY),
"grouping_charts": checked(plexpy.CONFIG.GROUPING_CHARTS)
}
return serve_template(templatename="config.html", title="Settings", config=config)
@cherrypy.expose
def configUpdate(self, **kwargs):
# Handle the variable config options. Note - keys with False values aren't getting passed
checked_configs = [
"launch_browser", "enable_https", "api_enabled", "freeze_db", "growl_enabled",
"prowl_enabled", "xbmc_enabled", "lms_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",
"grouping_global_history", "grouping_user_history", "grouping_charts"
]
for checked_config in checked_configs:
if checked_config not in kwargs:
# checked items should be zero or one. if they were not sent then the item was not checked
kwargs[checked_config] = 0
# Write Plex token to the config
if (not plexpy.CONFIG.PMS_TOKEN or plexpy.CONFIG.PMS_TOKEN == '' \
or kwargs['pms_username'] != plexpy.CONFIG.PMS_USERNAME) \
and (kwargs['pms_username'] != '' or kwargs['pms_password'] != ''):
plex_tv = plextv.PlexTV(kwargs['pms_username'], kwargs['pms_password'])
token = plex_tv.get_token()
if token:
kwargs['pms_token'] = token
logger.info('Plex.tv token sucessfully written to config.')
else:
logger.warn('Unable to write Plex.tv token to config.')
# Clear Plex token if username or password set to blank
if kwargs['pms_username'] == '' or kwargs['pms_password'] == '':
kwargs['pms_token'] = ''
# If passwords exists in config, do not overwrite when blank value received
if kwargs['http_password'] == ' ' and plexpy.CONFIG.HTTP_PASSWORD != '':
kwargs['http_password'] = plexpy.CONFIG.HTTP_PASSWORD
if kwargs['pms_password'] == ' ' and plexpy.CONFIG.PMS_PASSWORD != '':
kwargs['pms_password'] = plexpy.CONFIG.PMS_PASSWORD
for plain_config, use_config in [(x[4:], x) for x in kwargs if x.startswith('use_')]:
# the use prefix is fairly nice in the html, but does not match the actual config
kwargs[plain_config] = kwargs[use_config]
del kwargs[use_config]
plexpy.CONFIG.process_kwargs(kwargs)
# Write the config
plexpy.CONFIG.write()
# Reconfigure scheduler
plexpy.initialize_scheduler()
raise cherrypy.HTTPRedirect("config")
@cherrypy.expose
def do_state_change(self, signal, title, timer):
plexpy.SIGNAL = signal
message = title + '...'
return serve_template(templatename="shutdown.html", title=title,
message=message, timer=timer)
@cherrypy.expose
def get_history(self, start=0, length=100, custom_where='', **kwargs):
if 'user' in kwargs:
user = kwargs.get('user', "")
custom_where = 'user = "%s"' % user
if 'rating_key' in kwargs:
rating_key = kwargs.get('rating_key', "")
custom_where = 'rating_key = %s' % rating_key
plex_watch = plexwatch.PlexWatch()
history = plex_watch.get_history(start, length, kwargs, custom_where)
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps(history)
@cherrypy.expose
def get_stream_details(self, rating_key=0, **kwargs):
plex_watch = plexwatch.PlexWatch()
stream_details = plex_watch.get_stream_details(rating_key)
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps(stream_details)
@cherrypy.expose
def shutdown(self):
return self.do_state_change('shutdown', 'Shutting Down', 15)
@cherrypy.expose
def restart(self):
return self.do_state_change('restart', 'Restarting', 30)
@cherrypy.expose
def update(self):
return self.do_state_change('update', 'Updating', 120)
@cherrypy.expose
def api(self, *args, **kwargs):
from plexpy.api import Api
a = Api()
a.checkParams(*args, **kwargs)
return a.fetchData()
@cherrypy.expose
def twitterStep1(self):
cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store"
tweet = notifiers.TwitterNotifier()
return tweet._get_authorization()
@cherrypy.expose
def twitterStep2(self, key):
cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store"
tweet = notifiers.TwitterNotifier()
result = tweet._get_credentials(key)
logger.info(u"result: " + str(result))
if result:
return "Key verification successful"
else:
return "Unable to verify key"
@cherrypy.expose
def testTwitter(self):
cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store"
tweet = notifiers.TwitterNotifier()
result = tweet.test_notify()
if result:
return "Tweet successful, check your twitter to make sure it worked"
else:
return "Error sending tweet"
@cherrypy.expose
def osxnotifyregister(self, app):
cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store"
from osxnotify import registerapp as osxnotify
result, msg = osxnotify.registerapp(app)
if result:
osx_notify = notifiers.OSX_NOTIFY()
osx_notify.notify('Registered', result, 'Success :-)')
logger.info('Registered %s, to re-register a different app, delete this app first' % result)
else:
logger.warn(msg)
return msg
@cherrypy.expose
def get_pms_token(self):
token = plextv.PlexTV()
result = token.get_token()
if result:
return result
else:
logger.warn('Unable to retrieve Plex.tv token.')
return False
@cherrypy.expose
def get_pms_sessions_json(self, **kwargs):
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_sessions('json')
if result:
cherrypy.response.headers['Content-type'] = 'application/json'
return result
else:
logger.warn('Unable to retrieve data.')
return False
@cherrypy.expose
def get_current_activity(self, **kwargs):
try:
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_current_activity()
except:
return serve_template(templatename="current_activity.html", activity=None)
if result:
return serve_template(templatename="current_activity.html", activity=result)
else:
return serve_template(templatename="current_activity.html", activity=None)
logger.warn('Unable to retrieve data.')
@cherrypy.expose
def get_current_activity_header(self, **kwargs):
try:
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_current_activity()
except IOError, e:
return serve_template(templatename="current_activity_header.html", activity=None)
if result:
return serve_template(templatename="current_activity_header.html", activity=result['stream_count'])
else:
return serve_template(templatename="current_activity_header.html", activity=None)
logger.warn('Unable to retrieve data.')
@cherrypy.expose
def get_recently_added(self, count='0', **kwargs):
try:
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_recently_added_details(count)
except IOError, e:
return serve_template(templatename="recently_added.html", recently_added=None)
if result:
return serve_template(templatename="recently_added.html", recently_added=result['recently_added'])
else:
return serve_template(templatename="recently_added.html", recently_added=None)
logger.warn('Unable to retrieve data.')
@cherrypy.expose
def pms_image_proxy(self, img='', width='0', height='0', **kwargs):
if img != '':
try:
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_image(img, width, height)
logger.debug(u'Image proxy queried. Content type is %s' % result[0])
cherrypy.response.headers['Content-type'] = result[0]
return result[1]
except:
logger.warn('Image proxy queried but errors occured.')
return 'No image'
else:
logger.warn('Image proxy queried but no parameters received.')
return 'No image'
@cherrypy.expose
def info(self, rating_key='', **kwargs):
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_metadata_details(rating_key)
if result:
return serve_template(templatename="info.html", metadata=result['metadata'], title="Info")
else:
return serve_template(templatename="info.html", metadata='', title="Info")
logger.warn('Unable to retrieve data.')
@cherrypy.expose
def get_user_recently_watched(self, user=None, limit='10', **kwargs):
plex_watch = plexwatch.PlexWatch()
result = plex_watch.get_recently_watched(user, limit)
if result:
return serve_template(templatename="user_recently_watched.html", recently_watched=result, title="Recently Watched")
else:
return serve_template(templatename="user_recently_watched.html", metadata='', title="Recently Watched")
logger.warn('Unable to retrieve data.')
@cherrypy.expose
def get_metadata_json(self, rating_key='', **kwargs):
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_metadata(rating_key, 'json')
if result:
cherrypy.response.headers['Content-type'] = 'application/json'
return result
else:
logger.warn('Unable to retrieve data.')
@cherrypy.expose
def get_metadata_xml(self, rating_key='', **kwargs):
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_metadata(rating_key)
if result:
cherrypy.response.headers['Content-type'] = 'application/xml'
return result
else:
logger.warn('Unable to retrieve data.')
@cherrypy.expose
def get_recently_added_json(self, count='0', **kwargs):
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_recently_added(count, 'json')
if result:
cherrypy.response.headers['Content-type'] = 'application/json'
return result
else:
logger.warn('Unable to retrieve data.')
@cherrypy.expose
def get_stream(self, row_id='', **kwargs):
plex_watch = plexwatch.PlexWatch()
result = plex_watch.get_stream_details('122')
if result:
cherrypy.response.headers['Content-type'] = 'application/json'
return result
else:
logger.warn('Unable to retrieve data.')
@cherrypy.expose
def get_user_ips(self, start=0, length=100, custom_where='', **kwargs):
if 'user' in kwargs:
user = kwargs.get('user', "")
custom_where = 'user = "%s"' % user
plex_watch = plexwatch.PlexWatch()
history = plex_watch.get_user_unique_ips(start, length, kwargs, custom_where)
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps(history)
@cherrypy.expose
def get_watched(self, user=None, limit='10', **kwargs):
plex_watch = plexwatch.PlexWatch()
result = plex_watch.get_recently_watched(user, limit)
if result:
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps(result)
else:
logger.warn('Unable to retrieve data.')