diff --git a/data/interfaces/default/logs.html b/data/interfaces/default/logs.html index d795ac26..3747d549 100644 --- a/data/interfaces/default/logs.html +++ b/data/interfaces/default/logs.html @@ -21,6 +21,7 @@ Logs
+
@@ -120,7 +121,7 @@ LoadPlexPyLogs(); clearSearchButton('log_table', log_table); }); - + function LoadPlexPyLogs() { log_table_options.ajax = { url: "getLog" @@ -157,6 +158,7 @@ $("#plexpy-logs-btn").click(function () { $("#clear-logs").show(); + $("#download-plexpylog").show() $("#clear-notify-logs").hide(); LoadPlexPyLogs(); clearSearchButton('log_table', log_table); @@ -164,6 +166,7 @@ $("#plex-logs-btn").click(function () { $("#clear-logs").hide(); + $("#download-plexpylog").hide() $("#clear-notify-logs").hide(); LoadPlexLogs(); clearSearchButton('plex_log_table', plex_log_table); @@ -171,6 +174,7 @@ $("#plex-scanner-logs-btn").click(function () { $("#clear-logs").hide(); + $("#download-plexpylog").hide() $("#clear-notify-logs").hide(); LoadPlexScannerLogs(); clearSearchButton('plex_scanner_log_table', plex_scanner_log_table); @@ -178,6 +182,7 @@ $("#notification-logs-btn").click(function () { $("#clear-logs").hide(); + $("#download-plexpylog").hide() $("#clear-notify-logs").show(); LoadNotificationLogs(); 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 () { var r = confirm("Are you sure you want to clear the PlexPy notification log?"); if (r == true) { diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 5995f259..d07cd117 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -67,8 +67,6 @@ CONFIG_FILE = None DB_FILE = None -LOG_LIST = [] - INSTALL_TYPE = None CURRENT_VERSION = None LATEST_VERSION = None @@ -134,7 +132,7 @@ def initialize(config_file): try: os.makedirs(CONFIG.BACKUP_DIR) 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: CONFIG.CACHE_DIR = os.path.join(DATA_DIR, 'cache') @@ -142,14 +140,14 @@ def initialize(config_file): try: os.makedirs(CONFIG.CACHE_DIR) 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 logger.info('Checking to see if the database has all tables....') try: dbcheck() 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 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: fp.write(CURRENT_VERSION) except IOError as e: - logger.error("Unable to write current version to file '%s': %s", - version_lock_file, e) + logger.error("Unable to write current version to file '%s': %s" % + (version_lock_file, e)) # Check for new versions if CONFIG.CHECK_GITHUB_ON_STARTUP and CONFIG.CHECK_GITHUB: @@ -219,7 +217,7 @@ def daemonize(): pid = os.fork() # @UndefinedVariable - only available in UNIX if pid != 0: sys.exit(0) - except OSError, e: + except OSError as e: raise RuntimeError("1st fork failed: %s [%d]", e.strerror, e.errno) os.setsid() @@ -233,7 +231,7 @@ def daemonize(): pid = os.fork() # @UndefinedVariable - only available in UNIX if pid != 0: sys.exit(0) - except OSError, e: + except OSError as e: raise RuntimeError("2nd fork failed: %s [%d]", e.strerror, e.errno) dev_null = file('/dev/null', 'r') @@ -269,7 +267,7 @@ def launch_browser(host, port, root): try: webbrowser.open('%s://%s:%i%s' % (protocol, host, port, root)) except Exception as e: - logger.error('Could not launch browser: %s', e) + logger.error('Could not launch browser: %s' % e) def initialize_scheduler(): @@ -949,7 +947,7 @@ def shutdown(restart=False, update=False): try: versioncheck.update() except Exception as e: - logger.warn('PlexPy failed to update: %s. Restarting.', e) + logger.warn('PlexPy failed to update: %s. Restarting.' % e) if CREATEPID: logger.info('Removing pidfile %s', PIDFILE) @@ -963,7 +961,7 @@ def shutdown(restart=False, update=False): if '--nolaunch' not in args: args += ['--nolaunch'] logger.info('Restarting PlexPy with %s', args) - + # os.execv fails with spaced names on Windows # https://bugs.python.org/issue19066 if os.name == 'nt': diff --git a/plexpy/api2.py b/plexpy/api2.py index b0d085a1..65ca2ee5 100644 --- a/plexpy/api2.py +++ b/plexpy/api2.py @@ -443,6 +443,9 @@ General optional parameters: if self._api_cmd == 'docs_md': return out['response']['data'] + elif self._api_cmd == 'download_log': + return + if self._api_out_type == 'json': cherrypy.response.headers['Content-Type'] = 'application/json;charset=UTF-8' try: diff --git a/plexpy/logger.py b/plexpy/logger.py index 0ab56bf1..b43e06d3 100644 --- a/plexpy/logger.py +++ b/plexpy/logger.py @@ -31,7 +31,7 @@ import helpers # These settings are for file logging only FILENAME = "plexpy.log" -MAX_SIZE = 1000000 # 1 MB +MAX_SIZE = 5000000 # 5 MB MAX_FILES = 5 _BLACKLIST_WORDS = [] @@ -42,18 +42,6 @@ logger = logging.getLogger("plexpy") # Global queue for multiprocessing logging queue = None -class LogListHandler(logging.Handler): - """ - Log handler for Web UI. - """ - - def emit(self, record): - message = self.format(record) - message = message.replace("\n", "
") - - plexpy.LOG_LIST.insert(0, (helpers.now(), message, record.levelname, record.threadName)) - - class NoThreadFilter(logging.Filter): """ Log filter for the current thread @@ -209,12 +197,6 @@ def initLogger(console=False, log_dir=False, verbose=False): logger.propagate = False 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 if log_dir: filename = os.path.join(log_dir, FILENAME) diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index 67e4b502..182df2e5 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -1890,7 +1890,7 @@ class PmsConnect(object): 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. Array contains the image content type and image binary @@ -1900,51 +1900,24 @@ class PmsConnect(object): height { the image height } 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 - image_uri = img.rsplit('/', 1)[0] - - # 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 img: + uri = '/photo/:/transcode?url=http://127.0.0.1:32400%s' % img 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: - logger.error(u"PlexPy Pmsconnect :: Image proxy queried but no width or height specified.") - raise Exception + return result[0], result[1] - request, content_type = self.request_handler.make_request(uri=uri, - proto=self.protocol, - 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 + else: + logger.error(u"PlexPy Pmsconnect :: Image proxy queries but no input received.") def get_search_results(self, query=''): """ diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 4591c967..5cc22958 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -20,6 +20,9 @@ import random import threading import cherrypy +from cherrypy.lib.static import serve_file, serve_download +from cherrypy._cperror import NotFound + from hashing_passwords import make_hash from mako.lookup import TemplateLookup from mako import exceptions @@ -59,7 +62,7 @@ def serve_template(templatename, **kwargs): try: 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) except: return exceptions.html_error_template().render() @@ -68,7 +71,7 @@ def serve_template(templatename, **kwargs): class WebInterface(object): auth = AuthController() - + def __init__(self): self.interface_dir = os.path.join(str(plexpy.PROG_DIR), 'data/') @@ -133,12 +136,12 @@ class WebInterface(object): Returns: json: - [{"clientIdentifier": "ds48g4r354a8v9byrrtr697g3g79w", - "httpsRequired": "0", - "ip": "xxx.xxx.xxx.xxx", - "label": "Winterfell-Server", - "local": "1", - "port": "32400", + [{"clientIdentifier": "ds48g4r354a8v9byrrtr697g3g79w", + "httpsRequired": "0", + "ip": "xxx.xxx.xxx.xxx", + "label": "Winterfell-Server", + "local": "1", + "port": "32400", "value": "xxx.xxx.xxx.xxx" }, {...}, @@ -296,7 +299,7 @@ class WebInterface(object): try: pms_connect = pmsconnect.PmsConnect() 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) if result: @@ -309,7 +312,7 @@ class WebInterface(object): @cherrypy.tools.json_out() @requireAuth(member_of("admin")) def delete_temp_sessions(self): - + result = database.delete_sessions() if result: @@ -354,30 +357,30 @@ class WebInterface(object): "recordsTotal": 10, "recordsFiltered": 10, "data": - [{"child_count": 3745, - "content_rating": "TV-MA", - "count": 62, - "do_notify": "Checked", - "do_notify_created": "Checked", - "duration": 1578037, - "id": 1128, - "keep_history": "Checked", - "labels": [], - "last_accessed": 1462693216, - "last_played": "Game of Thrones - The Red Woman", - "library_art": "/:/resources/show-fanart.jpg", - "library_thumb": "", - "media_index": 1, - "media_type": "episode", - "parent_count": 240, - "parent_media_index": 6, - "parent_title": "", - "plays": 772, - "rating_key": 153037, - "section_id": 2, - "section_name": "TV Shows", - "section_type": "Show", - "thumb": "/library/metadata/153036/thumb/1462175062", + [{"child_count": 3745, + "content_rating": "TV-MA", + "count": 62, + "do_notify": "Checked", + "do_notify_created": "Checked", + "duration": 1578037, + "id": 1128, + "keep_history": "Checked", + "labels": [], + "last_accessed": 1462693216, + "last_played": "Game of Thrones - The Red Woman", + "library_art": "/:/resources/show-fanart.jpg", + "library_thumb": "", + "media_index": 1, + "media_type": "episode", + "parent_count": 240, + "parent_media_index": 6, + "parent_title": "", + "plays": 772, + "rating_key": 153037, + "section_id": 2, + "section_name": "TV Shows", + "section_type": "Show", + "thumb": "/library/metadata/153036/thumb/1462175062", "year": 2016 }, {...}, @@ -623,27 +626,27 @@ class WebInterface(object): "filtered_file_size": 2616760056742, "total_file_size": 2616760056742, "data": - [{"added_at": "1403553078", - "audio_channels": "", - "audio_codec": "", - "bitrate": "", - "container": "", - "file_size": 253660175293, - "grandparent_rating_key": "", - "last_played": 1462380698, - "media_index": "1", - "media_type": "show", - "parent_media_index": "", - "parent_rating_key": "", - "play_count": 15, - "rating_key": "1219", - "section_id": 2, - "section_type": "show", - "thumb": "/library/metadata/1219/thumb/1436265995", - "title": "Game of Thrones", - "video_codec": "", - "video_framerate": "", - "video_resolution": "", + [{"added_at": "1403553078", + "audio_channels": "", + "audio_codec": "", + "bitrate": "", + "container": "", + "file_size": 253660175293, + "grandparent_rating_key": "", + "last_played": 1462380698, + "media_index": "1", + "media_type": "show", + "parent_media_index": "", + "parent_rating_key": "", + "play_count": 15, + "rating_key": "1219", + "section_id": 2, + "section_type": "show", + "thumb": "/library/metadata/1219/thumb/1436265995", + "title": "Game of Thrones", + "video_codec": "", + "video_framerate": "", + "video_resolution": "", "year": "2011" }, {...}, @@ -894,29 +897,29 @@ class WebInterface(object): "recordsTotal": 10, "recordsFiltered": 10, "data": - [{"allow_guest": "Checked", - "do_notify": "Checked", - "duration": 2998290, - "friendly_name": "Jon Snow", - "id": 1121, - "ip_address": "xxx.xxx.xxx.xxx", - "keep_history": "Checked", - "last_played": "Game of Thrones - The Red Woman", - "last_seen": 1462591869, - "media_index": 1, - "media_type": "episode", - "parent_media_index": 6, - "parent_title": "", - "platform": "Chrome", - "player": "Plex Web (Chrome)", - "plays": 487, - "rating_key": 153037, - "thumb": "/library/metadata/153036/thumb/1462175062", - "transcode_decision": "transcode", - "user_id": 328871, - "user_thumb": "https://plex.tv/users/568gwwoib5t98a3a/avatar", + [{"allow_guest": "Checked", + "do_notify": "Checked", + "duration": 2998290, + "friendly_name": "Jon Snow", + "id": 1121, + "ip_address": "xxx.xxx.xxx.xxx", + "keep_history": "Checked", + "last_played": "Game of Thrones - The Red Woman", + "last_seen": 1462591869, + "media_index": 1, + "media_type": "episode", + "parent_media_index": 6, + "parent_title": "", + "platform": "Chrome", + "player": "Plex Web (Chrome)", + "plays": 487, + "rating_key": 153037, + "thumb": "/library/metadata/153036/thumb/1462175062", + "transcode_decision": "transcode", + "user_id": 328871, + "user_thumb": "https://plex.tv/users/568gwwoib5t98a3a/avatar", "year": 2016 - }, + }, {...}, {...} ] @@ -1104,24 +1107,24 @@ class WebInterface(object): "recordsTotal": 2344, "recordsFiltered": 10, "data": - [{"friendly_name": "Jon Snow", - "id": 1121, - "ip_address": "xxx.xxx.xxx.xxx", - "last_played": "Game of Thrones - The Red Woman", - "last_seen": 1462591869, - "media_index": 1, - "media_type": "episode", - "parent_media_index": 6, - "parent_title": "", - "platform": "Chrome", - "play_count": 149, - "player": "Plex Web (Chrome)", - "rating_key": 153037, - "thumb": "/library/metadata/153036/thumb/1462175062", - "transcode_decision": "transcode", - "user_id": 328871, + [{"friendly_name": "Jon Snow", + "id": 1121, + "ip_address": "xxx.xxx.xxx.xxx", + "last_played": "Game of Thrones - The Red Woman", + "last_seen": 1462591869, + "media_index": 1, + "media_type": "episode", + "parent_media_index": 6, + "parent_title": "", + "platform": "Chrome", + "play_count": 149, + "player": "Plex Web (Chrome)", + "rating_key": 153037, + "thumb": "/library/metadata/153036/thumb/1462175062", + "transcode_decision": "transcode", + "user_id": 328871, "year": 2016 - }, + }, {...}, {...} ] @@ -1227,7 +1230,7 @@ class WebInterface(object): if delete_row: return {'message': delete_row} elif username: - delete_row = delete_user.undelete(username=username) + delete_row = user_data.undelete(username=username) if delete_row: return {'message': delete_row} @@ -1440,7 +1443,7 @@ class WebInterface(object): plexpy.CONFIG.write() return "Updated graphs config values." - + @cherrypy.expose @cherrypy.tools.json_out() @requireAuth() @@ -1468,7 +1471,7 @@ class WebInterface(object): user_names = user_data.get_user_names(kwargs=kwargs) return user_names - + @cherrypy.expose @cherrypy.tools.json_out() @requireAuth() @@ -1902,11 +1905,17 @@ class WebInterface(object): ##### Logs ##### + @cherrypy.expose + def makeerror(self): + try: + 1 / 0 + except Exception as e: + logger.exception(e) @cherrypy.expose @requireAuth(member_of("admin")) 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 @requireAuth(member_of("admin")) @@ -1930,11 +1939,26 @@ class WebInterface(object): if 'search[regex]' in kwargs: 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 = [] - if search_value == "": - filtered = plexpy.LOG_LIST[::] + if search_value == '': + filtered = filt 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 if order_column == '1': @@ -1944,11 +1968,10 @@ class WebInterface(object): 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), + 'recordsTotal': len(filt), 'data': rows, }) @@ -1969,8 +1992,8 @@ class WebInterface(object): Returns: json: - [["May 08, 2016 09:35:37", - "DEBUG", + [["May 08, 2016 09:35:37", + "DEBUG", "Auth: Came in with a super-token, authorization succeeded." ], [...], @@ -2013,20 +2036,20 @@ class WebInterface(object): "recordsTotal": 1039, "recordsFiltered": 163, "data": - [{"agent_id": 13, - "agent_name": "Telegram", - "body_text": "Game of Thrones - S06E01 - The Red Woman [Transcode].", - "id": 1000, - "notify_action": "play", - "poster_url": "http://i.imgur.com/ZSqS8Ri.jpg", - "rating_key": 153037, - "script_args": "[]", - "session_key": 147, - "subject_text": "PlexPy (Winterfell-Server)", - "timestamp": 1462253821, - "user": "DanyKhaleesi69", + [{"agent_id": 13, + "agent_name": "Telegram", + "body_text": "Game of Thrones - S06E01 - The Red Woman [Transcode].", + "id": 1000, + "notify_action": "play", + "poster_url": "http://i.imgur.com/ZSqS8Ri.jpg", + "rating_key": 153037, + "script_args": "[]", + "session_key": 147, + "subject_text": "PlexPy (Winterfell-Server)", + "timestamp": 1462253821, + "user": "DanyKhaleesi69", "user_id": 8008135 - }, + }, {...}, {...} ] @@ -2070,17 +2093,19 @@ class WebInterface(object): """ data_factory = datafactory.DataFactory() result = data_factory.delete_notification_log() + result = result if result else 'no data received' - if result: - return {'message': result} - else: - return {'message': 'no data received'} + return {'message': result} @cherrypy.expose @requireAuth(member_of("admin")) def clearLogs(self): - plexpy.LOG_LIST = [] - logger.info(u"Web logs cleared") + try: + 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") @cherrypy.expose @@ -2277,7 +2302,7 @@ class WebInterface(object): kwargs['http_hashed_password'] = 1 else: kwargs['http_password'] = plexpy.CONFIG.HTTP_PASSWORD - + elif kwargs['http_password'] and kwargs.get('http_hash_password'): kwargs['http_password'] = make_hash(kwargs['http_password']) kwargs['http_hashed_password'] = 1 @@ -2403,7 +2428,6 @@ class WebInterface(object): else: return {'message': 'Database backup failed.'} - @cherrypy.expose @requireAuth(member_of("admin")) def get_notification_agent_config(self, agent_id, **kwargs): @@ -2791,22 +2815,98 @@ class WebInterface(object): @cherrypy.expose @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() - result = pms_connect.get_image(img, width, height, fallback) + if not img and not ratingkey: + logger.debug('No image input received') + return - if result: - cherrypy.response.headers['Content-type'] = result[1] - return result[0] - else: - return None + if ratingkey and not img: + img = '/library/metadata/%s/thumb/1337' % ratingkey + + img_string = img.rsplit('/', 1)[0] + 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.tools.json_out() @requireAuth(member_of("admin")) def delete_poster_url(self, poster_url=''): - + if poster_url: data_factory = datafactory.DataFactory() result = data_factory.delete_poster_url(poster_url=poster_url) @@ -2839,7 +2939,7 @@ class WebInterface(object): Returns: json: - {"results_count": 69, + {"results_count": 69, "results_list": {"movie": [{...}, @@ -2883,8 +2983,8 @@ class WebInterface(object): return serve_template(templatename="info_search_results_list.html", data=None, title="Search Result List") - ##### Update Metadata ##### - + ##### Update Metadata ##### + @cherrypy.expose @requireAuth(member_of("admin")) def update_metadata(self, rating_key=None, query=None, update=False, **kwargs): @@ -3033,51 +3133,51 @@ class WebInterface(object): json: {"metadata": {"actors": [ - "Kit Harington", - "Emilia Clarke", - "Isaac Hempstead-Wright", - "Maisie Williams", - "Liam Cunningham", - ], - "added_at": "1461572396", - "art": "/library/metadata/1219/art/1462175063", - "content_rating": "TV-MA", + "Kit Harington", + "Emilia Clarke", + "Isaac Hempstead-Wright", + "Maisie Williams", + "Liam Cunningham", + ], + "added_at": "1461572396", + "art": "/library/metadata/1219/art/1462175063", + "content_rating": "TV-MA", "directors": [ "Jeremy Podeswa" - ], - "duration": "2998290", + ], + "duration": "2998290", "genres": [ - "Adventure", - "Drama", + "Adventure", + "Drama", "Fantasy" - ], - "grandparent_rating_key": "1219", - "grandparent_thumb": "/library/metadata/1219/thumb/1462175063", - "grandparent_title": "Game of Thrones", - "guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en", - "labels": [], - "last_viewed_at": "1462165717", - "library_name": "TV Shows", - "media_index": "1", - "media_type": "episode", - "originally_available_at": "2016-04-24", - "parent_media_index": "6", - "parent_rating_key": "153036", - "parent_thumb": "/library/metadata/153036/thumb/1462175062", - "parent_title": "", - "rating": "7.8", - "rating_key": "153037", - "section_id": "2", - "studio": "HBO", - "summary": "Jon Snow is dead. Daenerys meets a strong man. Cersei sees her daughter again.", - "tagline": "", - "thumb": "/library/metadata/153037/thumb/1462175060", - "title": "The Red Woman", - "updated_at": "1462175060", + ], + "grandparent_rating_key": "1219", + "grandparent_thumb": "/library/metadata/1219/thumb/1462175063", + "grandparent_title": "Game of Thrones", + "guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en", + "labels": [], + "last_viewed_at": "1462165717", + "library_name": "TV Shows", + "media_index": "1", + "media_type": "episode", + "originally_available_at": "2016-04-24", + "parent_media_index": "6", + "parent_rating_key": "153036", + "parent_thumb": "/library/metadata/153036/thumb/1462175062", + "parent_title": "", + "rating": "7.8", + "rating_key": "153037", + "section_id": "2", + "studio": "HBO", + "summary": "Jon Snow is dead. Daenerys meets a strong man. Cersei sees her daughter again.", + "tagline": "", + "thumb": "/library/metadata/153037/thumb/1462175060", + "title": "The Red Woman", + "updated_at": "1462175060", "writers": [ - "David Benioff", + "David Benioff", "D. B. Weiss" - ], + ], "year": "2016" } } @@ -3108,21 +3208,21 @@ class WebInterface(object): Returns: json: {"recently_added": - [{"added_at": "1461572396", - "grandparent_rating_key": "1219", - "grandparent_thumb": "/library/metadata/1219/thumb/1462175063", - "grandparent_title": "Game of Thrones", - "library_name": "", - "media_index": "1", - "media_type": "episode", - "parent_media_index": "6", - "parent_rating_key": "153036", - "parent_thumb": "/library/metadata/153036/thumb/1462175062", - "parent_title": "", - "rating_key": "153037", - "section_id": "2", - "thumb": "/library/metadata/153037/thumb/1462175060", - "title": "The Red Woman", + [{"added_at": "1461572396", + "grandparent_rating_key": "1219", + "grandparent_thumb": "/library/metadata/1219/thumb/1462175063", + "grandparent_title": "Game of Thrones", + "library_name": "", + "media_index": "1", + "media_type": "episode", + "parent_media_index": "6", + "parent_rating_key": "153036", + "parent_thumb": "/library/metadata/153036/thumb/1462175062", + "parent_title": "", + "rating_key": "153037", + "section_id": "2", + "thumb": "/library/metadata/153037/thumb/1462175060", + "title": "The Red Woman", "year": "2016" }, {...}, @@ -3312,60 +3412,60 @@ class WebInterface(object): json: {"stream_count": 3, "session": - [{"art": "/library/metadata/1219/art/1462175063", - "aspect_ratio": "1.78", - "audio_channels": "6", - "audio_codec": "ac3", - "audio_decision": "transcode", - "bif_thumb": "/library/parts/274169/indexes/sd/", - "bitrate": "10617", - "container": "mkv", - "content_rating": "TV-MA", - "duration": "2998290", - "friendly_name": "Mother of Dragons", - "grandparent_rating_key": "1219", - "grandparent_thumb": "/library/metadata/1219/thumb/1462175063", - "grandparent_title": "Game of Thrones", - "height": "1078", - "indexes": 1, - "ip_address": "xxx.xxx.xxx.xxx", - "labels": [], - "machine_id": "83f189w617623ccs6a1lqpby", - "media_index": "1", - "media_type": "episode", - "parent_media_index": "6", - "parent_rating_key": "153036", - "parent_thumb": "/library/metadata/153036/thumb/1462175062", - "parent_title": "", - "platform": "Chrome", - "player": "Plex Web (Chrome)", - "progress_percent": "0", - "rating_key": "153037", - "section_id": "2", - "session_key": "291", - "state": "playing", - "throttled": "1", - "thumb": "/library/metadata/153037/thumb/1462175060", - "title": "The Red Woman", - "transcode_audio_channels": "2", - "transcode_audio_codec": "aac", - "transcode_container": "mkv", - "transcode_height": "1078", - "transcode_key": "tiv5p524wcupe8nxegc26s9k9", - "transcode_progress": 2, - "transcode_protocol": "http", - "transcode_speed": "0.0", - "transcode_video_codec": "h264", - "transcode_width": "1920", - "user": "DanyKhaleesi69", - "user_id": 8008135, - "user_thumb": "https://plex.tv/users/568gwwoib5t98a3a/avatar", - "video_codec": "h264", - "video_decision": "copy", - "video_framerate": "24p", - "video_resolution": "1080", - "view_offset": "", - "width": "1920", + [{"art": "/library/metadata/1219/art/1462175063", + "aspect_ratio": "1.78", + "audio_channels": "6", + "audio_codec": "ac3", + "audio_decision": "transcode", + "bif_thumb": "/library/parts/274169/indexes/sd/", + "bitrate": "10617", + "container": "mkv", + "content_rating": "TV-MA", + "duration": "2998290", + "friendly_name": "Mother of Dragons", + "grandparent_rating_key": "1219", + "grandparent_thumb": "/library/metadata/1219/thumb/1462175063", + "grandparent_title": "Game of Thrones", + "height": "1078", + "indexes": 1, + "ip_address": "xxx.xxx.xxx.xxx", + "labels": [], + "machine_id": "83f189w617623ccs6a1lqpby", + "media_index": "1", + "media_type": "episode", + "parent_media_index": "6", + "parent_rating_key": "153036", + "parent_thumb": "/library/metadata/153036/thumb/1462175062", + "parent_title": "", + "platform": "Chrome", + "player": "Plex Web (Chrome)", + "progress_percent": "0", + "rating_key": "153037", + "section_id": "2", + "session_key": "291", + "state": "playing", + "throttled": "1", + "thumb": "/library/metadata/153037/thumb/1462175060", + "title": "The Red Woman", + "transcode_audio_channels": "2", + "transcode_audio_codec": "aac", + "transcode_container": "mkv", + "transcode_height": "1078", + "transcode_key": "tiv5p524wcupe8nxegc26s9k9", + "transcode_progress": 2, + "transcode_protocol": "http", + "transcode_speed": "0.0", + "transcode_video_codec": "h264", + "transcode_width": "1920", + "user": "DanyKhaleesi69", + "user_id": 8008135, + "user_thumb": "https://plex.tv/users/568gwwoib5t98a3a/avatar", + "video_codec": "h264", + "video_decision": "copy", + "video_framerate": "24p", + "video_resolution": "1080", + "view_offset": "", + "width": "1920", "year": "2016" }, {...}, @@ -3398,13 +3498,13 @@ class WebInterface(object): Returns: json: - [{"art": "/:/resources/show-fanart.jpg", - "child_count": "3745", - "count": "62", - "parent_count": "240", + [{"art": "/:/resources/show-fanart.jpg", + "child_count": "3745", + "count": "62", + "parent_count": "240", "section_id": "2", - "section_name": "TV Shows", - "section_type": "show", + "section_name": "TV Shows", + "section_type": "show", "thumb": "/:/resources/show.png" }, {...}, @@ -3436,17 +3536,17 @@ class WebInterface(object): Returns: json: - [{"email": "Jon.Snow.1337@CastleBlack.com", - "filter_all": "", - "filter_movies": "", - "filter_music": "", - "filter_photos": "", - "filter_tv": "", - "is_allow_sync": null, - "is_home_user": "1", - "is_restricted": "0", - "thumb": "https://plex.tv/users/k10w42309cynaopq/avatar", - "user_id": "328871", + [{"email": "Jon.Snow.1337@CastleBlack.com", + "filter_all": "", + "filter_movies": "", + "filter_music": "", + "filter_photos": "", + "filter_tv": "", + "is_allow_sync": null, + "is_home_user": "1", + "is_restricted": "0", + "thumb": "https://plex.tv/users/k10w42309cynaopq/avatar", + "user_id": "328871", "username": "Jon Snow" }, {...}, @@ -3478,26 +3578,26 @@ class WebInterface(object): Returns: json: - [{"content_type": "video", - "device_name": "Tyrion's iPad", - "failure": "", - "friendly_name": "Tyrion Lannister", - "item_complete_count": "0", - "item_count": "1", - "item_downloaded_count": "0", - "item_downloaded_percent_complete": 0, - "metadata_type": "movie", - "music_bitrate": "192", - "photo_quality": "74", - "platform": "iOS", - "rating_key": "154092", - "root_title": "Deadpool", - "state": "pending", - "sync_id": "11617019", - "title": "Deadpool", - "total_size": "0", - "user_id": "328871", - "username": "DrukenDwarfMan", + [{"content_type": "video", + "device_name": "Tyrion's iPad", + "failure": "", + "friendly_name": "Tyrion Lannister", + "item_complete_count": "0", + "item_count": "1", + "item_downloaded_count": "0", + "item_downloaded_percent_complete": 0, + "metadata_type": "movie", + "music_bitrate": "192", + "photo_quality": "74", + "platform": "iOS", + "rating_key": "154092", + "root_title": "Deadpool", + "state": "pending", + "sync_id": "11617019", + "title": "Deadpool", + "total_size": "0", + "user_id": "328871", + "username": "DrukenDwarfMan", "video_quality": "60" }, {...}, @@ -3555,22 +3655,22 @@ class WebInterface(object): {"stat_id": "top_tv", "stat_type": "total_plays", "rows": - [{"content_rating": "TV-MA", - "friendly_name": "", - "grandparent_thumb": "/library/metadata/1219/thumb/1462175063", - "labels": [], - "last_play": 1462380698, - "media_type": "episode", - "platform": "", - "platform_type": "", - "rating_key": 1219, - "row_id": 1116, - "section_id": 2, - "thumb": "", - "title": "Game of Thrones", - "total_duration": 213302, - "total_plays": 69, - "user": "", + [{"content_rating": "TV-MA", + "friendly_name": "", + "grandparent_thumb": "/library/metadata/1219/thumb/1462175063", + "labels": [], + "last_play": 1462380698, + "media_type": "episode", + "platform": "", + "platform_type": "", + "rating_key": 1219, + "row_id": 1116, + "section_id": 2, + "thumb": "", + "title": "Game of Thrones", + "total_duration": 213302, + "total_plays": 69, + "user": "", "users_watched": "" }, {...}, @@ -3678,4 +3778,4 @@ class WebInterface(object): def check_pms_updater(self): pms_connect = pmsconnect.PmsConnect() result = pms_connect.get_update_staus() - return result \ No newline at end of file + return result diff --git a/plexpy/webstart.py b/plexpy/webstart.py index 0ffc6a8a..fc28d5ca 100644 --- a/plexpy/webstart.py +++ b/plexpy/webstart.py @@ -75,7 +75,7 @@ def initialize(options): plexpy.HTTP_ROOT = options['http_root'] = '/' else: plexpy.HTTP_ROOT = options['http_root'] = '/' + options['http_root'].strip('/') + '/' - + cherrypy.config.update(options_dict) conf = { @@ -178,6 +178,17 @@ def initialize(options): 'tools.auth.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': { 'tools.staticfile.on': True, 'tools.staticfile.filename': os.path.abspath(os.path.join(plexpy.PROG_DIR, 'data/interfaces/default/images/favicon.ico')),