cache image, download log etc.

This commit is contained in:
Hellowlol 2016-05-09 00:44:02 +02:00
parent 21fcbd85d8
commit 9ae441b75a
7 changed files with 452 additions and 375 deletions

View file

@ -21,6 +21,7 @@
<span><i class="fa fa-list-alt"></i> Logs</span> <span><i class="fa fa-list-alt"></i> Logs</span>
</div> </div>
<div class="button-bar"> <div class="button-bar">
<button class="btn btn-dark" id="download-plexpylog"><i class="fa fa-download"></i> Download log</button>
<button class="btn btn-dark" id="clear-logs"><i class="fa fa-trash-o"></i> Clear log</button> <button class="btn btn-dark" id="clear-logs"><i class="fa fa-trash-o"></i> Clear log</button>
<button class="btn btn-dark" id="clear-notify-logs" style="display: none;"><i class="fa fa-trash-o"></i> Clear log</button> <button class="btn btn-dark" id="clear-notify-logs" style="display: none;"><i class="fa fa-trash-o"></i> Clear log</button>
</div> </div>
@ -120,7 +121,7 @@
LoadPlexPyLogs(); LoadPlexPyLogs();
clearSearchButton('log_table', log_table); clearSearchButton('log_table', log_table);
}); });
function LoadPlexPyLogs() { function LoadPlexPyLogs() {
log_table_options.ajax = { log_table_options.ajax = {
url: "getLog" url: "getLog"
@ -157,6 +158,7 @@
$("#plexpy-logs-btn").click(function () { $("#plexpy-logs-btn").click(function () {
$("#clear-logs").show(); $("#clear-logs").show();
$("#download-plexpylog").show()
$("#clear-notify-logs").hide(); $("#clear-notify-logs").hide();
LoadPlexPyLogs(); LoadPlexPyLogs();
clearSearchButton('log_table', log_table); clearSearchButton('log_table', log_table);
@ -164,6 +166,7 @@
$("#plex-logs-btn").click(function () { $("#plex-logs-btn").click(function () {
$("#clear-logs").hide(); $("#clear-logs").hide();
$("#download-plexpylog").hide()
$("#clear-notify-logs").hide(); $("#clear-notify-logs").hide();
LoadPlexLogs(); LoadPlexLogs();
clearSearchButton('plex_log_table', plex_log_table); clearSearchButton('plex_log_table', plex_log_table);
@ -171,6 +174,7 @@
$("#plex-scanner-logs-btn").click(function () { $("#plex-scanner-logs-btn").click(function () {
$("#clear-logs").hide(); $("#clear-logs").hide();
$("#download-plexpylog").hide()
$("#clear-notify-logs").hide(); $("#clear-notify-logs").hide();
LoadPlexScannerLogs(); LoadPlexScannerLogs();
clearSearchButton('plex_scanner_log_table', plex_scanner_log_table); clearSearchButton('plex_scanner_log_table', plex_scanner_log_table);
@ -178,6 +182,7 @@
$("#notification-logs-btn").click(function () { $("#notification-logs-btn").click(function () {
$("#clear-logs").hide(); $("#clear-logs").hide();
$("#download-plexpylog").hide()
$("#clear-notify-logs").show(); $("#clear-notify-logs").show();
LoadNotificationLogs(); LoadNotificationLogs();
clearSearchButton('notification_log_table', notification_log_table); clearSearchButton('notification_log_table', notification_log_table);
@ -190,6 +195,11 @@
} }
}); });
$("#download-plexpylog").click(function () {
window.location.href = "download_log";
});
$("#clear-notify-logs").click(function () { $("#clear-notify-logs").click(function () {
var r = confirm("Are you sure you want to clear the PlexPy notification log?"); var r = confirm("Are you sure you want to clear the PlexPy notification log?");
if (r == true) { if (r == true) {

View file

@ -67,8 +67,6 @@ CONFIG_FILE = None
DB_FILE = None DB_FILE = None
LOG_LIST = []
INSTALL_TYPE = None INSTALL_TYPE = None
CURRENT_VERSION = None CURRENT_VERSION = None
LATEST_VERSION = None LATEST_VERSION = None
@ -134,7 +132,7 @@ def initialize(config_file):
try: try:
os.makedirs(CONFIG.BACKUP_DIR) os.makedirs(CONFIG.BACKUP_DIR)
except OSError as e: except OSError as e:
logger.error("Could not create backup dir '%s': %s", BACKUP_DIR, e) logger.error("Could not create backup dir '%s': %s" % (BACKUP_DIR, e))
if not CONFIG.CACHE_DIR: if not CONFIG.CACHE_DIR:
CONFIG.CACHE_DIR = os.path.join(DATA_DIR, 'cache') CONFIG.CACHE_DIR = os.path.join(DATA_DIR, 'cache')
@ -142,14 +140,14 @@ def initialize(config_file):
try: try:
os.makedirs(CONFIG.CACHE_DIR) os.makedirs(CONFIG.CACHE_DIR)
except OSError as e: except OSError as e:
logger.error("Could not create cache dir '%s': %s", CACHE_DIR, e) logger.error("Could not create cache dir '%s': %s" % (CACHE_DIR, e))
# Initialize the database # Initialize the database
logger.info('Checking to see if the database has all tables....') logger.info('Checking to see if the database has all tables....')
try: try:
dbcheck() dbcheck()
except Exception as e: except Exception as e:
logger.error("Can't connect to the database: %s", e) logger.error("Can't connect to the database: %s" % e)
# Check if PlexPy has a uuid # Check if PlexPy has a uuid
if CONFIG.PMS_UUID == '' or not CONFIG.PMS_UUID: if CONFIG.PMS_UUID == '' or not CONFIG.PMS_UUID:
@ -171,8 +169,8 @@ def initialize(config_file):
with open(version_lock_file, "w") as fp: with open(version_lock_file, "w") as fp:
fp.write(CURRENT_VERSION) fp.write(CURRENT_VERSION)
except IOError as e: except IOError as e:
logger.error("Unable to write current version to file '%s': %s", logger.error("Unable to write current version to file '%s': %s" %
version_lock_file, e) (version_lock_file, e))
# Check for new versions # Check for new versions
if CONFIG.CHECK_GITHUB_ON_STARTUP and CONFIG.CHECK_GITHUB: if CONFIG.CHECK_GITHUB_ON_STARTUP and CONFIG.CHECK_GITHUB:
@ -219,7 +217,7 @@ def daemonize():
pid = os.fork() # @UndefinedVariable - only available in UNIX pid = os.fork() # @UndefinedVariable - only available in UNIX
if pid != 0: if pid != 0:
sys.exit(0) sys.exit(0)
except OSError, e: except OSError as e:
raise RuntimeError("1st fork failed: %s [%d]", e.strerror, e.errno) raise RuntimeError("1st fork failed: %s [%d]", e.strerror, e.errno)
os.setsid() os.setsid()
@ -233,7 +231,7 @@ def daemonize():
pid = os.fork() # @UndefinedVariable - only available in UNIX pid = os.fork() # @UndefinedVariable - only available in UNIX
if pid != 0: if pid != 0:
sys.exit(0) sys.exit(0)
except OSError, e: except OSError as e:
raise RuntimeError("2nd fork failed: %s [%d]", e.strerror, e.errno) raise RuntimeError("2nd fork failed: %s [%d]", e.strerror, e.errno)
dev_null = file('/dev/null', 'r') dev_null = file('/dev/null', 'r')
@ -269,7 +267,7 @@ def launch_browser(host, port, root):
try: try:
webbrowser.open('%s://%s:%i%s' % (protocol, host, port, root)) webbrowser.open('%s://%s:%i%s' % (protocol, host, port, root))
except Exception as e: except Exception as e:
logger.error('Could not launch browser: %s', e) logger.error('Could not launch browser: %s' % e)
def initialize_scheduler(): def initialize_scheduler():
@ -949,7 +947,7 @@ def shutdown(restart=False, update=False):
try: try:
versioncheck.update() versioncheck.update()
except Exception as e: except Exception as e:
logger.warn('PlexPy failed to update: %s. Restarting.', e) logger.warn('PlexPy failed to update: %s. Restarting.' % e)
if CREATEPID: if CREATEPID:
logger.info('Removing pidfile %s', PIDFILE) logger.info('Removing pidfile %s', PIDFILE)
@ -963,7 +961,7 @@ def shutdown(restart=False, update=False):
if '--nolaunch' not in args: if '--nolaunch' not in args:
args += ['--nolaunch'] args += ['--nolaunch']
logger.info('Restarting PlexPy with %s', args) logger.info('Restarting PlexPy with %s', args)
# os.execv fails with spaced names on Windows # os.execv fails with spaced names on Windows
# https://bugs.python.org/issue19066 # https://bugs.python.org/issue19066
if os.name == 'nt': if os.name == 'nt':

View file

@ -443,6 +443,9 @@ General optional parameters:
if self._api_cmd == 'docs_md': if self._api_cmd == 'docs_md':
return out['response']['data'] return out['response']['data']
elif self._api_cmd == 'download_log':
return
if self._api_out_type == 'json': if self._api_out_type == 'json':
cherrypy.response.headers['Content-Type'] = 'application/json;charset=UTF-8' cherrypy.response.headers['Content-Type'] = 'application/json;charset=UTF-8'
try: try:

View file

@ -31,7 +31,7 @@ import helpers
# These settings are for file logging only # These settings are for file logging only
FILENAME = "plexpy.log" FILENAME = "plexpy.log"
MAX_SIZE = 1000000 # 1 MB MAX_SIZE = 5000000 # 5 MB
MAX_FILES = 5 MAX_FILES = 5
_BLACKLIST_WORDS = [] _BLACKLIST_WORDS = []
@ -42,18 +42,6 @@ logger = logging.getLogger("plexpy")
# Global queue for multiprocessing logging # Global queue for multiprocessing logging
queue = None queue = None
class LogListHandler(logging.Handler):
"""
Log handler for Web UI.
"""
def emit(self, record):
message = self.format(record)
message = message.replace("\n", "<br />")
plexpy.LOG_LIST.insert(0, (helpers.now(), message, record.levelname, record.threadName))
class NoThreadFilter(logging.Filter): class NoThreadFilter(logging.Filter):
""" """
Log filter for the current thread Log filter for the current thread
@ -209,12 +197,6 @@ def initLogger(console=False, log_dir=False, verbose=False):
logger.propagate = False logger.propagate = False
logger.setLevel(logging.DEBUG if verbose else logging.INFO) logger.setLevel(logging.DEBUG if verbose else logging.INFO)
# Add list logger
loglist_handler = LogListHandler()
loglist_handler.setLevel(logging.DEBUG)
logger.addHandler(loglist_handler)
# Setup file logger # Setup file logger
if log_dir: if log_dir:
filename = os.path.join(log_dir, FILENAME) filename = os.path.join(log_dir, FILENAME)

View file

@ -1890,7 +1890,7 @@ class PmsConnect(object):
return labels_list return labels_list
def get_image(self, img=None, width=None, height=None, fallback=None): def get_image(self, img=None, width=None, height=None):
""" """
Return image data as array. Return image data as array.
Array contains the image content type and image binary Array contains the image content type and image binary
@ -1900,51 +1900,24 @@ class PmsConnect(object):
height { the image height } height { the image height }
Output: array Output: array
""" """
if not img:
logger.error(u"PlexPy Pmsconnect :: Image proxy queried but no input received.")
return None
# Remove the timestamp from PMS image uri if img:
image_uri = img.rsplit('/', 1)[0] uri = '/photo/:/transcode?url=http://127.0.0.1:32400%s' % img
# Try to retrieve the image from cache if it isn't a bif index.
if not 'indexes' in image_uri:
image_path, content_type = helpers.cache_image(image_uri)
if image_path and content_type:
return [open(image_path, 'rb'), content_type]
try:
if width.isdigit() and height.isdigit(): if width.isdigit() and height.isdigit():
uri = '/photo/:/transcode?url=http://127.0.0.1:32400' + img + '&width=' + width + '&height=' + height uri += '&width=%s&height=%s' % (width, height)
result = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET',
return_type=True)
if result is None:
return
else: else:
logger.error(u"PlexPy Pmsconnect :: Image proxy queried but no width or height specified.") return result[0], result[1]
raise Exception
request, content_type = self.request_handler.make_request(uri=uri, else:
proto=self.protocol, logger.error(u"PlexPy Pmsconnect :: Image proxy queries but no input received.")
request_type='GET',
return_type=True)
# Save the image to cache if it isn't a bif index.
if not 'indexes' in image_uri:
helpers.cache_image(image_uri, request)
return [request, content_type]
except Exception as e:
if fallback:
logger.debug(u"PlexPy Pmsconnect :: Trying fallback %s image." % fallback)
try:
if fallback == 'poster':
return [open(common.DEFAULT_POSTER_THUMB, 'rb'), 'image/png']
elif fallback == 'cover':
return [open(common.DEFAULT_COVER_THUMB, 'rb'), 'image/png']
elif fallback == 'art':
return [open(common.DEFAULT_ART, 'rb'), 'image/png']
except IOError as e:
logger.error(u"PlexPy Pmsconnect :: Unable to read fallback %s image: %s" % (fallback, e))
return None
def get_search_results(self, query=''): def get_search_results(self, query=''):
""" """

View file

@ -20,6 +20,9 @@ import random
import threading import threading
import cherrypy import cherrypy
from cherrypy.lib.static import serve_file, serve_download
from cherrypy._cperror import NotFound
from hashing_passwords import make_hash from hashing_passwords import make_hash
from mako.lookup import TemplateLookup from mako.lookup import TemplateLookup
from mako import exceptions from mako import exceptions
@ -59,7 +62,7 @@ def serve_template(templatename, **kwargs):
try: try:
template = _hplookup.get_template(templatename) template = _hplookup.get_template(templatename)
return template.render(http_root=plexpy.HTTP_ROOT, server_name=server_name, return template.render(http_root=plexpy.HTTP_ROOT, server_name=server_name,
_session=_session, **kwargs) _session=_session, **kwargs)
except: except:
return exceptions.html_error_template().render() return exceptions.html_error_template().render()
@ -68,7 +71,7 @@ def serve_template(templatename, **kwargs):
class WebInterface(object): class WebInterface(object):
auth = AuthController() auth = AuthController()
def __init__(self): def __init__(self):
self.interface_dir = os.path.join(str(plexpy.PROG_DIR), 'data/') self.interface_dir = os.path.join(str(plexpy.PROG_DIR), 'data/')
@ -133,12 +136,12 @@ class WebInterface(object):
Returns: Returns:
json: json:
[{"clientIdentifier": "ds48g4r354a8v9byrrtr697g3g79w", [{"clientIdentifier": "ds48g4r354a8v9byrrtr697g3g79w",
"httpsRequired": "0", "httpsRequired": "0",
"ip": "xxx.xxx.xxx.xxx", "ip": "xxx.xxx.xxx.xxx",
"label": "Winterfell-Server", "label": "Winterfell-Server",
"local": "1", "local": "1",
"port": "32400", "port": "32400",
"value": "xxx.xxx.xxx.xxx" "value": "xxx.xxx.xxx.xxx"
}, },
{...}, {...},
@ -296,7 +299,7 @@ class WebInterface(object):
try: try:
pms_connect = pmsconnect.PmsConnect() pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_recently_added_details(count=count) result = pms_connect.get_recently_added_details(count=count)
except IOError, e: except IOError as e:
return serve_template(templatename="recently_added.html", data=None) return serve_template(templatename="recently_added.html", data=None)
if result: if result:
@ -309,7 +312,7 @@ class WebInterface(object):
@cherrypy.tools.json_out() @cherrypy.tools.json_out()
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
def delete_temp_sessions(self): def delete_temp_sessions(self):
result = database.delete_sessions() result = database.delete_sessions()
if result: if result:
@ -354,30 +357,30 @@ class WebInterface(object):
"recordsTotal": 10, "recordsTotal": 10,
"recordsFiltered": 10, "recordsFiltered": 10,
"data": "data":
[{"child_count": 3745, [{"child_count": 3745,
"content_rating": "TV-MA", "content_rating": "TV-MA",
"count": 62, "count": 62,
"do_notify": "Checked", "do_notify": "Checked",
"do_notify_created": "Checked", "do_notify_created": "Checked",
"duration": 1578037, "duration": 1578037,
"id": 1128, "id": 1128,
"keep_history": "Checked", "keep_history": "Checked",
"labels": [], "labels": [],
"last_accessed": 1462693216, "last_accessed": 1462693216,
"last_played": "Game of Thrones - The Red Woman", "last_played": "Game of Thrones - The Red Woman",
"library_art": "/:/resources/show-fanart.jpg", "library_art": "/:/resources/show-fanart.jpg",
"library_thumb": "", "library_thumb": "",
"media_index": 1, "media_index": 1,
"media_type": "episode", "media_type": "episode",
"parent_count": 240, "parent_count": 240,
"parent_media_index": 6, "parent_media_index": 6,
"parent_title": "", "parent_title": "",
"plays": 772, "plays": 772,
"rating_key": 153037, "rating_key": 153037,
"section_id": 2, "section_id": 2,
"section_name": "TV Shows", "section_name": "TV Shows",
"section_type": "Show", "section_type": "Show",
"thumb": "/library/metadata/153036/thumb/1462175062", "thumb": "/library/metadata/153036/thumb/1462175062",
"year": 2016 "year": 2016
}, },
{...}, {...},
@ -623,27 +626,27 @@ class WebInterface(object):
"filtered_file_size": 2616760056742, "filtered_file_size": 2616760056742,
"total_file_size": 2616760056742, "total_file_size": 2616760056742,
"data": "data":
[{"added_at": "1403553078", [{"added_at": "1403553078",
"audio_channels": "", "audio_channels": "",
"audio_codec": "", "audio_codec": "",
"bitrate": "", "bitrate": "",
"container": "", "container": "",
"file_size": 253660175293, "file_size": 253660175293,
"grandparent_rating_key": "", "grandparent_rating_key": "",
"last_played": 1462380698, "last_played": 1462380698,
"media_index": "1", "media_index": "1",
"media_type": "show", "media_type": "show",
"parent_media_index": "", "parent_media_index": "",
"parent_rating_key": "", "parent_rating_key": "",
"play_count": 15, "play_count": 15,
"rating_key": "1219", "rating_key": "1219",
"section_id": 2, "section_id": 2,
"section_type": "show", "section_type": "show",
"thumb": "/library/metadata/1219/thumb/1436265995", "thumb": "/library/metadata/1219/thumb/1436265995",
"title": "Game of Thrones", "title": "Game of Thrones",
"video_codec": "", "video_codec": "",
"video_framerate": "", "video_framerate": "",
"video_resolution": "", "video_resolution": "",
"year": "2011" "year": "2011"
}, },
{...}, {...},
@ -894,29 +897,29 @@ class WebInterface(object):
"recordsTotal": 10, "recordsTotal": 10,
"recordsFiltered": 10, "recordsFiltered": 10,
"data": "data":
[{"allow_guest": "Checked", [{"allow_guest": "Checked",
"do_notify": "Checked", "do_notify": "Checked",
"duration": 2998290, "duration": 2998290,
"friendly_name": "Jon Snow", "friendly_name": "Jon Snow",
"id": 1121, "id": 1121,
"ip_address": "xxx.xxx.xxx.xxx", "ip_address": "xxx.xxx.xxx.xxx",
"keep_history": "Checked", "keep_history": "Checked",
"last_played": "Game of Thrones - The Red Woman", "last_played": "Game of Thrones - The Red Woman",
"last_seen": 1462591869, "last_seen": 1462591869,
"media_index": 1, "media_index": 1,
"media_type": "episode", "media_type": "episode",
"parent_media_index": 6, "parent_media_index": 6,
"parent_title": "", "parent_title": "",
"platform": "Chrome", "platform": "Chrome",
"player": "Plex Web (Chrome)", "player": "Plex Web (Chrome)",
"plays": 487, "plays": 487,
"rating_key": 153037, "rating_key": 153037,
"thumb": "/library/metadata/153036/thumb/1462175062", "thumb": "/library/metadata/153036/thumb/1462175062",
"transcode_decision": "transcode", "transcode_decision": "transcode",
"user_id": 328871, "user_id": 328871,
"user_thumb": "https://plex.tv/users/568gwwoib5t98a3a/avatar", "user_thumb": "https://plex.tv/users/568gwwoib5t98a3a/avatar",
"year": 2016 "year": 2016
}, },
{...}, {...},
{...} {...}
] ]
@ -1104,24 +1107,24 @@ class WebInterface(object):
"recordsTotal": 2344, "recordsTotal": 2344,
"recordsFiltered": 10, "recordsFiltered": 10,
"data": "data":
[{"friendly_name": "Jon Snow", [{"friendly_name": "Jon Snow",
"id": 1121, "id": 1121,
"ip_address": "xxx.xxx.xxx.xxx", "ip_address": "xxx.xxx.xxx.xxx",
"last_played": "Game of Thrones - The Red Woman", "last_played": "Game of Thrones - The Red Woman",
"last_seen": 1462591869, "last_seen": 1462591869,
"media_index": 1, "media_index": 1,
"media_type": "episode", "media_type": "episode",
"parent_media_index": 6, "parent_media_index": 6,
"parent_title": "", "parent_title": "",
"platform": "Chrome", "platform": "Chrome",
"play_count": 149, "play_count": 149,
"player": "Plex Web (Chrome)", "player": "Plex Web (Chrome)",
"rating_key": 153037, "rating_key": 153037,
"thumb": "/library/metadata/153036/thumb/1462175062", "thumb": "/library/metadata/153036/thumb/1462175062",
"transcode_decision": "transcode", "transcode_decision": "transcode",
"user_id": 328871, "user_id": 328871,
"year": 2016 "year": 2016
}, },
{...}, {...},
{...} {...}
] ]
@ -1227,7 +1230,7 @@ class WebInterface(object):
if delete_row: if delete_row:
return {'message': delete_row} return {'message': delete_row}
elif username: elif username:
delete_row = delete_user.undelete(username=username) delete_row = user_data.undelete(username=username)
if delete_row: if delete_row:
return {'message': delete_row} return {'message': delete_row}
@ -1440,7 +1443,7 @@ class WebInterface(object):
plexpy.CONFIG.write() plexpy.CONFIG.write()
return "Updated graphs config values." return "Updated graphs config values."
@cherrypy.expose @cherrypy.expose
@cherrypy.tools.json_out() @cherrypy.tools.json_out()
@requireAuth() @requireAuth()
@ -1468,7 +1471,7 @@ class WebInterface(object):
user_names = user_data.get_user_names(kwargs=kwargs) user_names = user_data.get_user_names(kwargs=kwargs)
return user_names return user_names
@cherrypy.expose @cherrypy.expose
@cherrypy.tools.json_out() @cherrypy.tools.json_out()
@requireAuth() @requireAuth()
@ -1902,11 +1905,17 @@ class WebInterface(object):
##### Logs ##### ##### Logs #####
@cherrypy.expose
def makeerror(self):
try:
1 / 0
except Exception as e:
logger.exception(e)
@cherrypy.expose @cherrypy.expose
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
def logs(self): def logs(self):
return serve_template(templatename="logs.html", title="Log", lineList=plexpy.LOG_LIST) return serve_template(templatename="logs.html", title="Log")
@cherrypy.expose @cherrypy.expose
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
@ -1930,11 +1939,26 @@ class WebInterface(object):
if 'search[regex]' in kwargs: if 'search[regex]' in kwargs:
search_regex = kwargs.get('search[regex]', "") search_regex = kwargs.get('search[regex]', "")
filt = []
fa = filt.append
with open(os.path.join(plexpy.CONFIG.LOG_DIR, 'plexpy.log')) as f:
for l in f.readlines():
try:
temp_loglevel_and_time = l.split('- ')
loglvl = temp_loglevel_and_time[1].split(' :')[0].strip()
msg = l.split(' : ')[1].replace('\n', '')
fa([temp_loglevel_and_time[0], loglvl, msg])
except IndexError:
# Add traceback message to previous msg..
tl = (len(filt) - 1)
filt[tl][2] += ' ' + l.replace('\n', '')
continue
filtered = [] filtered = []
if search_value == "": if search_value == '':
filtered = plexpy.LOG_LIST[::] filtered = filt
else: else:
filtered = [row for row in plexpy.LOG_LIST for column in row if search_value.lower() in column.lower()] filtered = [row for row in filt for column in row if search_value.lower() in column.lower()]
sortcolumn = 0 sortcolumn = 0
if order_column == '1': if order_column == '1':
@ -1944,11 +1968,10 @@ class WebInterface(object):
filtered.sort(key=lambda x: x[sortcolumn], reverse=order_dir == "desc") filtered.sort(key=lambda x: x[sortcolumn], reverse=order_dir == "desc")
rows = filtered[start:(start + length)] rows = filtered[start:(start + length)]
rows = [[row[0], row[2], row[1]] for row in rows]
return json.dumps({ return json.dumps({
'recordsFiltered': len(filtered), 'recordsFiltered': len(filtered),
'recordsTotal': len(plexpy.LOG_LIST), 'recordsTotal': len(filt),
'data': rows, 'data': rows,
}) })
@ -1969,8 +1992,8 @@ class WebInterface(object):
Returns: Returns:
json: json:
[["May 08, 2016 09:35:37", [["May 08, 2016 09:35:37",
"DEBUG", "DEBUG",
"Auth: Came in with a super-token, authorization succeeded." "Auth: Came in with a super-token, authorization succeeded."
], ],
[...], [...],
@ -2013,20 +2036,20 @@ class WebInterface(object):
"recordsTotal": 1039, "recordsTotal": 1039,
"recordsFiltered": 163, "recordsFiltered": 163,
"data": "data":
[{"agent_id": 13, [{"agent_id": 13,
"agent_name": "Telegram", "agent_name": "Telegram",
"body_text": "Game of Thrones - S06E01 - The Red Woman [Transcode].", "body_text": "Game of Thrones - S06E01 - The Red Woman [Transcode].",
"id": 1000, "id": 1000,
"notify_action": "play", "notify_action": "play",
"poster_url": "http://i.imgur.com/ZSqS8Ri.jpg", "poster_url": "http://i.imgur.com/ZSqS8Ri.jpg",
"rating_key": 153037, "rating_key": 153037,
"script_args": "[]", "script_args": "[]",
"session_key": 147, "session_key": 147,
"subject_text": "PlexPy (Winterfell-Server)", "subject_text": "PlexPy (Winterfell-Server)",
"timestamp": 1462253821, "timestamp": 1462253821,
"user": "DanyKhaleesi69", "user": "DanyKhaleesi69",
"user_id": 8008135 "user_id": 8008135
}, },
{...}, {...},
{...} {...}
] ]
@ -2070,17 +2093,19 @@ class WebInterface(object):
""" """
data_factory = datafactory.DataFactory() data_factory = datafactory.DataFactory()
result = data_factory.delete_notification_log() result = data_factory.delete_notification_log()
result = result if result else 'no data received'
if result: return {'message': result}
return {'message': result}
else:
return {'message': 'no data received'}
@cherrypy.expose @cherrypy.expose
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
def clearLogs(self): def clearLogs(self):
plexpy.LOG_LIST = [] try:
logger.info(u"Web logs cleared") open(os.path.join(plexpy.CONFIG.LOG_DIR, 'plexpy.log'), 'w').close()
except Exception as e:
logger.exception(u'Failed to delete plexpy.log %s' % e)
logger.info(u'plexpy.log cleared')
raise cherrypy.HTTPRedirect("logs") raise cherrypy.HTTPRedirect("logs")
@cherrypy.expose @cherrypy.expose
@ -2277,7 +2302,7 @@ class WebInterface(object):
kwargs['http_hashed_password'] = 1 kwargs['http_hashed_password'] = 1
else: else:
kwargs['http_password'] = plexpy.CONFIG.HTTP_PASSWORD kwargs['http_password'] = plexpy.CONFIG.HTTP_PASSWORD
elif kwargs['http_password'] and kwargs.get('http_hash_password'): elif kwargs['http_password'] and kwargs.get('http_hash_password'):
kwargs['http_password'] = make_hash(kwargs['http_password']) kwargs['http_password'] = make_hash(kwargs['http_password'])
kwargs['http_hashed_password'] = 1 kwargs['http_hashed_password'] = 1
@ -2403,7 +2428,6 @@ class WebInterface(object):
else: else:
return {'message': 'Database backup failed.'} return {'message': 'Database backup failed.'}
@cherrypy.expose @cherrypy.expose
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
def get_notification_agent_config(self, agent_id, **kwargs): def get_notification_agent_config(self, agent_id, **kwargs):
@ -2791,22 +2815,98 @@ class WebInterface(object):
@cherrypy.expose @cherrypy.expose
@requireAuth() @requireAuth()
def pms_image_proxy(self, img='', width='0', height='0', fallback=None, **kwargs): def pms_image_proxy(self, img='', ratingkey=None, width='0', height='0', fallback=None, **kwargs):
""" Grabs the images from pms and saved them to disk """
pms_connect = pmsconnect.PmsConnect() if not img and not ratingkey:
result = pms_connect.get_image(img, width, height, fallback) logger.debug('No image input received')
return
if result: if ratingkey and not img:
cherrypy.response.headers['Content-type'] = result[1] img = '/library/metadata/%s/thumb/1337' % ratingkey
return result[0]
else: img_string = img.rsplit('/', 1)[0]
return None img_string += '%s%s' % (width, height)
fp = hashlib.md5(img_string).hexdigest()
fp += '.jpg' # we want to be able to preview the thumbs
c_dir = os.path.join(plexpy.CONFIG.CACHE_DIR, 'images')
ffp = os.path.join(c_dir, fp)
if not os.path.exists(c_dir):
os.mkdir(c_dir)
try:
if 'indexes' in img:
raise
return serve_file(path=ffp, content_type='image/jpeg')
except NotFound:
# the image does not exist, download it from pms
try:
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_image(img, width, height)
if result and result[0]:
cherrypy.response.headers['Content-type'] = result[1]
if 'indexes' not in img:
with open(ffp, 'wb') as f:
f.write(result[0])
return result[0]
else:
raise
except Exception as e:
logger.debug('Failed to get image %s file %s falling back to %s' % (img, fp, e))
fbi = None
if fallback == 'poster':
fbi = common.DEFAULT_POSTER_THUMB
elif fallback == 'cover':
fbi = common.DEFAULT_COVER_THUMB
elif fallback == 'art':
fbi = common.DEFAULT_ART
if fbi:
fp = os.path.join(plexpy.PROG_DIR, 'data', fbi)
return serve_file(path=fp, content_type='image/png')
@cherrypy.expose
@requireAuth(member_of("admin"))
@addtoapi()
def download_log(self):
try:
logger.logger.flush()
except:
pass
return serve_download(os.path.join(plexpy.CONFIG.LOG_DIR, 'plexpy.log'), name='plexpy.log')
@cherrypy.expose
@requireAuth(member_of("admin"))
@addtoapi()
def delete_image_cache(self):
""" Deletes the image cache dir and recreates it """
cache_dir = os.path.join(plexpy.CONFIG.CACHE_DIR, 'images')
try:
os.rmdir(cache_dir)
except OSError as e:
logger.exception('Failed to delete %s %s' % (cache_dir, e))
return
try:
os.makedirs(cache_dir)
except OSError as e:
logger.exception('Faild to make %s %s' % (cache_dir, e))
return
return {'result': 'success', 'message': 'Deleted image cache'}
@cherrypy.expose @cherrypy.expose
@cherrypy.tools.json_out() @cherrypy.tools.json_out()
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
def delete_poster_url(self, poster_url=''): def delete_poster_url(self, poster_url=''):
if poster_url: if poster_url:
data_factory = datafactory.DataFactory() data_factory = datafactory.DataFactory()
result = data_factory.delete_poster_url(poster_url=poster_url) result = data_factory.delete_poster_url(poster_url=poster_url)
@ -2839,7 +2939,7 @@ class WebInterface(object):
Returns: Returns:
json: json:
{"results_count": 69, {"results_count": 69,
"results_list": "results_list":
{"movie": {"movie":
[{...}, [{...},
@ -2883,8 +2983,8 @@ class WebInterface(object):
return serve_template(templatename="info_search_results_list.html", data=None, title="Search Result List") return serve_template(templatename="info_search_results_list.html", data=None, title="Search Result List")
##### Update Metadata ##### ##### Update Metadata #####
@cherrypy.expose @cherrypy.expose
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
def update_metadata(self, rating_key=None, query=None, update=False, **kwargs): def update_metadata(self, rating_key=None, query=None, update=False, **kwargs):
@ -3033,51 +3133,51 @@ class WebInterface(object):
json: json:
{"metadata": {"metadata":
{"actors": [ {"actors": [
"Kit Harington", "Kit Harington",
"Emilia Clarke", "Emilia Clarke",
"Isaac Hempstead-Wright", "Isaac Hempstead-Wright",
"Maisie Williams", "Maisie Williams",
"Liam Cunningham", "Liam Cunningham",
], ],
"added_at": "1461572396", "added_at": "1461572396",
"art": "/library/metadata/1219/art/1462175063", "art": "/library/metadata/1219/art/1462175063",
"content_rating": "TV-MA", "content_rating": "TV-MA",
"directors": [ "directors": [
"Jeremy Podeswa" "Jeremy Podeswa"
], ],
"duration": "2998290", "duration": "2998290",
"genres": [ "genres": [
"Adventure", "Adventure",
"Drama", "Drama",
"Fantasy" "Fantasy"
], ],
"grandparent_rating_key": "1219", "grandparent_rating_key": "1219",
"grandparent_thumb": "/library/metadata/1219/thumb/1462175063", "grandparent_thumb": "/library/metadata/1219/thumb/1462175063",
"grandparent_title": "Game of Thrones", "grandparent_title": "Game of Thrones",
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en", "guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
"labels": [], "labels": [],
"last_viewed_at": "1462165717", "last_viewed_at": "1462165717",
"library_name": "TV Shows", "library_name": "TV Shows",
"media_index": "1", "media_index": "1",
"media_type": "episode", "media_type": "episode",
"originally_available_at": "2016-04-24", "originally_available_at": "2016-04-24",
"parent_media_index": "6", "parent_media_index": "6",
"parent_rating_key": "153036", "parent_rating_key": "153036",
"parent_thumb": "/library/metadata/153036/thumb/1462175062", "parent_thumb": "/library/metadata/153036/thumb/1462175062",
"parent_title": "", "parent_title": "",
"rating": "7.8", "rating": "7.8",
"rating_key": "153037", "rating_key": "153037",
"section_id": "2", "section_id": "2",
"studio": "HBO", "studio": "HBO",
"summary": "Jon Snow is dead. Daenerys meets a strong man. Cersei sees her daughter again.", "summary": "Jon Snow is dead. Daenerys meets a strong man. Cersei sees her daughter again.",
"tagline": "", "tagline": "",
"thumb": "/library/metadata/153037/thumb/1462175060", "thumb": "/library/metadata/153037/thumb/1462175060",
"title": "The Red Woman", "title": "The Red Woman",
"updated_at": "1462175060", "updated_at": "1462175060",
"writers": [ "writers": [
"David Benioff", "David Benioff",
"D. B. Weiss" "D. B. Weiss"
], ],
"year": "2016" "year": "2016"
} }
} }
@ -3108,21 +3208,21 @@ class WebInterface(object):
Returns: Returns:
json: json:
{"recently_added": {"recently_added":
[{"added_at": "1461572396", [{"added_at": "1461572396",
"grandparent_rating_key": "1219", "grandparent_rating_key": "1219",
"grandparent_thumb": "/library/metadata/1219/thumb/1462175063", "grandparent_thumb": "/library/metadata/1219/thumb/1462175063",
"grandparent_title": "Game of Thrones", "grandparent_title": "Game of Thrones",
"library_name": "", "library_name": "",
"media_index": "1", "media_index": "1",
"media_type": "episode", "media_type": "episode",
"parent_media_index": "6", "parent_media_index": "6",
"parent_rating_key": "153036", "parent_rating_key": "153036",
"parent_thumb": "/library/metadata/153036/thumb/1462175062", "parent_thumb": "/library/metadata/153036/thumb/1462175062",
"parent_title": "", "parent_title": "",
"rating_key": "153037", "rating_key": "153037",
"section_id": "2", "section_id": "2",
"thumb": "/library/metadata/153037/thumb/1462175060", "thumb": "/library/metadata/153037/thumb/1462175060",
"title": "The Red Woman", "title": "The Red Woman",
"year": "2016" "year": "2016"
}, },
{...}, {...},
@ -3312,60 +3412,60 @@ class WebInterface(object):
json: json:
{"stream_count": 3, {"stream_count": 3,
"session": "session":
[{"art": "/library/metadata/1219/art/1462175063", [{"art": "/library/metadata/1219/art/1462175063",
"aspect_ratio": "1.78", "aspect_ratio": "1.78",
"audio_channels": "6", "audio_channels": "6",
"audio_codec": "ac3", "audio_codec": "ac3",
"audio_decision": "transcode", "audio_decision": "transcode",
"bif_thumb": "/library/parts/274169/indexes/sd/", "bif_thumb": "/library/parts/274169/indexes/sd/",
"bitrate": "10617", "bitrate": "10617",
"container": "mkv", "container": "mkv",
"content_rating": "TV-MA", "content_rating": "TV-MA",
"duration": "2998290", "duration": "2998290",
"friendly_name": "Mother of Dragons", "friendly_name": "Mother of Dragons",
"grandparent_rating_key": "1219", "grandparent_rating_key": "1219",
"grandparent_thumb": "/library/metadata/1219/thumb/1462175063", "grandparent_thumb": "/library/metadata/1219/thumb/1462175063",
"grandparent_title": "Game of Thrones", "grandparent_title": "Game of Thrones",
"height": "1078", "height": "1078",
"indexes": 1, "indexes": 1,
"ip_address": "xxx.xxx.xxx.xxx", "ip_address": "xxx.xxx.xxx.xxx",
"labels": [], "labels": [],
"machine_id": "83f189w617623ccs6a1lqpby", "machine_id": "83f189w617623ccs6a1lqpby",
"media_index": "1", "media_index": "1",
"media_type": "episode", "media_type": "episode",
"parent_media_index": "6", "parent_media_index": "6",
"parent_rating_key": "153036", "parent_rating_key": "153036",
"parent_thumb": "/library/metadata/153036/thumb/1462175062", "parent_thumb": "/library/metadata/153036/thumb/1462175062",
"parent_title": "", "parent_title": "",
"platform": "Chrome", "platform": "Chrome",
"player": "Plex Web (Chrome)", "player": "Plex Web (Chrome)",
"progress_percent": "0", "progress_percent": "0",
"rating_key": "153037", "rating_key": "153037",
"section_id": "2", "section_id": "2",
"session_key": "291", "session_key": "291",
"state": "playing", "state": "playing",
"throttled": "1", "throttled": "1",
"thumb": "/library/metadata/153037/thumb/1462175060", "thumb": "/library/metadata/153037/thumb/1462175060",
"title": "The Red Woman", "title": "The Red Woman",
"transcode_audio_channels": "2", "transcode_audio_channels": "2",
"transcode_audio_codec": "aac", "transcode_audio_codec": "aac",
"transcode_container": "mkv", "transcode_container": "mkv",
"transcode_height": "1078", "transcode_height": "1078",
"transcode_key": "tiv5p524wcupe8nxegc26s9k9", "transcode_key": "tiv5p524wcupe8nxegc26s9k9",
"transcode_progress": 2, "transcode_progress": 2,
"transcode_protocol": "http", "transcode_protocol": "http",
"transcode_speed": "0.0", "transcode_speed": "0.0",
"transcode_video_codec": "h264", "transcode_video_codec": "h264",
"transcode_width": "1920", "transcode_width": "1920",
"user": "DanyKhaleesi69", "user": "DanyKhaleesi69",
"user_id": 8008135, "user_id": 8008135,
"user_thumb": "https://plex.tv/users/568gwwoib5t98a3a/avatar", "user_thumb": "https://plex.tv/users/568gwwoib5t98a3a/avatar",
"video_codec": "h264", "video_codec": "h264",
"video_decision": "copy", "video_decision": "copy",
"video_framerate": "24p", "video_framerate": "24p",
"video_resolution": "1080", "video_resolution": "1080",
"view_offset": "", "view_offset": "",
"width": "1920", "width": "1920",
"year": "2016" "year": "2016"
}, },
{...}, {...},
@ -3398,13 +3498,13 @@ class WebInterface(object):
Returns: Returns:
json: json:
[{"art": "/:/resources/show-fanart.jpg", [{"art": "/:/resources/show-fanart.jpg",
"child_count": "3745", "child_count": "3745",
"count": "62", "count": "62",
"parent_count": "240", "parent_count": "240",
"section_id": "2", "section_id": "2",
"section_name": "TV Shows", "section_name": "TV Shows",
"section_type": "show", "section_type": "show",
"thumb": "/:/resources/show.png" "thumb": "/:/resources/show.png"
}, },
{...}, {...},
@ -3436,17 +3536,17 @@ class WebInterface(object):
Returns: Returns:
json: json:
[{"email": "Jon.Snow.1337@CastleBlack.com", [{"email": "Jon.Snow.1337@CastleBlack.com",
"filter_all": "", "filter_all": "",
"filter_movies": "", "filter_movies": "",
"filter_music": "", "filter_music": "",
"filter_photos": "", "filter_photos": "",
"filter_tv": "", "filter_tv": "",
"is_allow_sync": null, "is_allow_sync": null,
"is_home_user": "1", "is_home_user": "1",
"is_restricted": "0", "is_restricted": "0",
"thumb": "https://plex.tv/users/k10w42309cynaopq/avatar", "thumb": "https://plex.tv/users/k10w42309cynaopq/avatar",
"user_id": "328871", "user_id": "328871",
"username": "Jon Snow" "username": "Jon Snow"
}, },
{...}, {...},
@ -3478,26 +3578,26 @@ class WebInterface(object):
Returns: Returns:
json: json:
[{"content_type": "video", [{"content_type": "video",
"device_name": "Tyrion's iPad", "device_name": "Tyrion's iPad",
"failure": "", "failure": "",
"friendly_name": "Tyrion Lannister", "friendly_name": "Tyrion Lannister",
"item_complete_count": "0", "item_complete_count": "0",
"item_count": "1", "item_count": "1",
"item_downloaded_count": "0", "item_downloaded_count": "0",
"item_downloaded_percent_complete": 0, "item_downloaded_percent_complete": 0,
"metadata_type": "movie", "metadata_type": "movie",
"music_bitrate": "192", "music_bitrate": "192",
"photo_quality": "74", "photo_quality": "74",
"platform": "iOS", "platform": "iOS",
"rating_key": "154092", "rating_key": "154092",
"root_title": "Deadpool", "root_title": "Deadpool",
"state": "pending", "state": "pending",
"sync_id": "11617019", "sync_id": "11617019",
"title": "Deadpool", "title": "Deadpool",
"total_size": "0", "total_size": "0",
"user_id": "328871", "user_id": "328871",
"username": "DrukenDwarfMan", "username": "DrukenDwarfMan",
"video_quality": "60" "video_quality": "60"
}, },
{...}, {...},
@ -3555,22 +3655,22 @@ class WebInterface(object):
{"stat_id": "top_tv", {"stat_id": "top_tv",
"stat_type": "total_plays", "stat_type": "total_plays",
"rows": "rows":
[{"content_rating": "TV-MA", [{"content_rating": "TV-MA",
"friendly_name": "", "friendly_name": "",
"grandparent_thumb": "/library/metadata/1219/thumb/1462175063", "grandparent_thumb": "/library/metadata/1219/thumb/1462175063",
"labels": [], "labels": [],
"last_play": 1462380698, "last_play": 1462380698,
"media_type": "episode", "media_type": "episode",
"platform": "", "platform": "",
"platform_type": "", "platform_type": "",
"rating_key": 1219, "rating_key": 1219,
"row_id": 1116, "row_id": 1116,
"section_id": 2, "section_id": 2,
"thumb": "", "thumb": "",
"title": "Game of Thrones", "title": "Game of Thrones",
"total_duration": 213302, "total_duration": 213302,
"total_plays": 69, "total_plays": 69,
"user": "", "user": "",
"users_watched": "" "users_watched": ""
}, },
{...}, {...},
@ -3678,4 +3778,4 @@ class WebInterface(object):
def check_pms_updater(self): def check_pms_updater(self):
pms_connect = pmsconnect.PmsConnect() pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_update_staus() result = pms_connect.get_update_staus()
return result return result

View file

@ -75,7 +75,7 @@ def initialize(options):
plexpy.HTTP_ROOT = options['http_root'] = '/' plexpy.HTTP_ROOT = options['http_root'] = '/'
else: else:
plexpy.HTTP_ROOT = options['http_root'] = '/' + options['http_root'].strip('/') + '/' plexpy.HTTP_ROOT = options['http_root'] = '/' + options['http_root'].strip('/') + '/'
cherrypy.config.update(options_dict) cherrypy.config.update(options_dict)
conf = { conf = {
@ -178,6 +178,17 @@ def initialize(options):
'tools.auth.on': False, 'tools.auth.on': False,
'tools.sessions.on': False 'tools.sessions.on': False
}, },
'/pms_image_proxy': {
'tools.staticdir.on': True,
'tools.staticdir.dir': os.path.join(plexpy.CONFIG.CACHE_DIR, 'images'),
'tools.caching.on': True,
'tools.caching.force': True,
'tools.caching.delay': 0,
'tools.expires.on': True,
'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days
'tools.auth.on': False,
'tools.sessions.on': False
},
'/favicon.ico': { '/favicon.ico': {
'tools.staticfile.on': True, 'tools.staticfile.on': True,
'tools.staticfile.filename': os.path.abspath(os.path.join(plexpy.PROG_DIR, 'data/interfaces/default/images/favicon.ico')), 'tools.staticfile.filename': os.path.abspath(os.path.join(plexpy.PROG_DIR, 'data/interfaces/default/images/favicon.ico')),