diff --git a/data/interfaces/default/base.html b/data/interfaces/default/base.html index e756fd4e..8d577e3b 100644 --- a/data/interfaces/default/base.html +++ b/data/interfaces/default/base.html @@ -179,6 +179,11 @@ from plexpy import version % else:
  • % endif + % if title=="Libraries" or title=="Library": +
  • Libraries
  • + % else: +
  • Libraries
  • + % endif % if title=="Users" or title=="User":
  • Users
  • % else: diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 7c20f941..37032b0a 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -498,6 +498,16 @@ textarea.form-control:focus { -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); } +.libraries-poster-face { + overflow: hidden; + float: left; + background-size: contain; + height: 40px; + width: 40px; + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); +} a .poster-face:hover, a .cover-face:hover, a .users-poster-face:hover { @@ -2127,7 +2137,8 @@ a .home-platforms-instance-list-oval:hover, float: right; } .colvis-button-bar, -.refresh-users-button { +.refresh-users-button, +.refresh-libraries-button { float: right; } .nav-settings, @@ -2359,17 +2370,21 @@ a .home-platforms-instance-list-oval:hover, background-size: cover; width: 80px; } -.edit-user-toggles { +.edit-user-toggles, +.edit-library-toggles { padding-right: 10px; } -.edit-user-toggles > input[type='checkbox'] { +.edit-user-toggles > input[type='checkbox'], +.edit-library-toggles > input[type='checkbox'] { display: none; } -.edit-user-toggles > input[type='checkbox'] + label { +.edit-user-toggles > input[type='checkbox'] + label, +.edit-library-toggles > input[type='checkbox'] + label { color: #444; cursor: pointer; } -.edit-user-toggles > input[type='checkbox']:checked + label { +.edit-user-toggles > input[type='checkbox']:checked + label, +.edit-library-toggles > input[type='checkbox']:checked + label { color: #fff; cursor: pointer; } @@ -2418,7 +2433,8 @@ a .home-platforms-instance-list-oval:hover, left: 12px; } #users-to-delete > li, -#users-to-purge > li { +#users-to-purge > li, +#libraries-to-purge > li { color: #e9a049; } #updatebar { diff --git a/data/interfaces/default/js/tables/libraries.js b/data/interfaces/default/js/tables/libraries.js new file mode 100644 index 00000000..1ca07f61 --- /dev/null +++ b/data/interfaces/default/js/tables/libraries.js @@ -0,0 +1,247 @@ +var libraries_to_purge = []; + +libraries_list_table_options = { + "language": { + "search": "Search: ", + "lengthMenu":"Show _MENU_ entries per page", + "info":"Showing _START_ to _END_ of _TOTAL_ active libraries", + "infoEmpty":"Showing 0 to 0 of 0 entries", + "infoFiltered":"", + "emptyTable": "No data in table", + }, + "destroy": true, + "processing": false, + "serverSide": true, + "pageLength": 10, + "order": [ 1, 'asc'], + "autoWidth": true, + "stateSave": true, + "pagingType": "bootstrap", + "columnDefs": [ + { + "targets": [0], + "data": null, + "createdCell": function (td, cellData, rowData, row, col) { + $(td).html('
    ' + + '   ' + + ' ' + + ' '); + }, + "width": "7%", + "className": "edit-control no-wrap hidden", + "searchable": false, + "orderable": false + }, + { + "targets": [1], + "data": "library_thumb", + "createdCell": function (td, cellData, rowData, row, col) { + if (cellData === '') { + $(td).html('
    '); + } else { + $(td).html('
    '); + } + }, + "orderable": false, + "searchable": false, + "width": "5%", + "className": "libraries-thumbs" + }, + { + "targets": [2], + "data": "section_name", + "createdCell": function (td, cellData, rowData, row, col) { + if (cellData !== '') { + $(td).html('
    ' + cellData + '
    '); + } else { + $(td).html(cellData); + } + }, + "width": "10%", + "className": "no-wrap" + }, + { + "targets": [3], + "data": "section_type", + "createdCell": function (td, cellData, rowData, row, col) { + if (cellData !== '') { + $(td).html(cellData); + } + }, + "width": "10%", + "className": "no-wrap hidden-xs" + }, + { + "targets": [4], + "data": "count", + "createdCell": function (td, cellData, rowData, row, col) { + if (cellData !== null) { + $(td).html(cellData); + } else { + $(td).html('n/a'); + } + + }, + "width": "10%", + "className": "no-wrap hidden-xs" + }, + { + "targets": [5], + "data": "parent_count", + "createdCell": function (td, cellData, rowData, row, col) { + if (cellData !== null) { + $(td).html(cellData); + } else { + $(td).html('n/a'); + } + + }, + "width": "10%", + "className": "no-wrap hidden-xs" + }, + { + "targets": [6], + "data": "child_count", + "createdCell": function (td, cellData, rowData, row, col) { + if (cellData !== null) { + $(td).html(cellData); + } else { + $(td).html('n/a'); + } + + }, + "width": "10%", + "className": "no-wrap hidden-xs" + }, + { + "targets": [7], + "data": "last_accessed", + "render": function (data, type, full) { + if (data) { + return moment(data, "X").fromNow(); + } else { + return "never"; + } + }, + "searchable": false, + "width": "10%", + "className": "no-wrap hidden-xs" + }, + { + "targets": [8], + "data":"last_watched", + "createdCell": function (td, cellData, rowData, row, col) { + if (cellData !== '') { + var media_type = ''; + var thumb_popover = '' + if (rowData['media_type'] === 'movie') { + media_type = ''; + thumb_popover = '' + cellData + '' + $(td).html('
    ' + media_type + ' ' + thumb_popover + '
    '); + } else if (rowData['media_type'] === 'episode') { + media_type = ''; + thumb_popover = '' + cellData + '' + $(td).html('
    ' + media_type + ' ' + thumb_popover + '
    '); + } else if (rowData['media_type'] === 'track') { + media_type = ''; + thumb_popover = '' + cellData + '' + $(td).html('
    ' + media_type + ' ' + thumb_popover + '
    '); + } else if (rowData['media_type']) { + $(td).html('' + cellData + ''); + } else { + $(td).html('n/a'); + } + } + }, + "width": "25%", + "className": "hidden-sm hidden-xs" + }, + { + "targets": [9], + "data": "plays", + "searchable": false, + "width": "10%" + } + + ], + "drawCallback": function (settings) { + // Jump to top of page + //$('html,body').scrollTop(0); + $('#ajaxMsg').fadeOut(); + + // Create the tooltips. + $('.purge-tooltip').tooltip(); + $('.edit-tooltip').tooltip(); + $('.transcode-tooltip').tooltip(); + $('.media-type-tooltip').tooltip(); + $('.thumb-tooltip').popover({ + html: true, + trigger: 'hover', + placement: 'right', + content: function () { + return '
    '; + } + }); + + if ($('#row-edit-mode').hasClass('active')) { + $('.edit-control').each(function () { + $(this).removeClass('hidden'); + }); + } + }, + "preDrawCallback": function(settings) { + var msg = " Fetching rows..."; + showMsg(msg, false, false, 0) + }, + "rowCallback": function (row, rowData) { + if ($.inArray(rowData['section_id'], libraries_to_purge) !== -1) { + $(row).find('button[data-id="' + rowData['section_id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger'); + } + } +} + +$('#libraries_list_table').on('change', 'td.edit-control > .edit-library-toggles > input', function () { + var tr = $(this).parents('tr'); + var row = libraries_list_table.row(tr); + var rowData = row.data(); + + var do_notify = 0; + var keep_history = 0; + if ($('#do_notify-' + rowData['section_id']).is(':checked')) { + do_notify = 1; + } + if ($('#keep_history-' + rowData['section_id']).is(':checked')) { + keep_history = 1; + } + + $.ajax({ + url: 'edit_library', + data: { + section_id: rowData['section_id'], + do_notify: do_notify, + keep_history: keep_history, + custom_thumb: rowData['library_thumb'] + }, + cache: false, + async: true, + success: function (data) { + var msg = "Library updated"; + showMsg(msg, false, true, 2000); + } + }); +}); + +$('#libraries_list_table').on('click', 'td.edit-control > .edit-library-toggles > button.purge-library', function () { + var tr = $(this).parents('tr'); + var row = libraries_list_table.row(tr); + var rowData = row.data(); + + var index_purge = $.inArray(rowData['section_id'], libraries_to_purge); + + if (index_purge === -1) { + libraries_to_purge.push(rowData['section_id']); + } else { + libraries_to_purge.splice(index_purge, 1); + } + $(this).toggleClass('btn-warning').toggleClass('btn-danger'); +}); \ No newline at end of file diff --git a/data/interfaces/default/libraries.html b/data/interfaces/default/libraries.html new file mode 100644 index 00000000..8d5022ab --- /dev/null +++ b/data/interfaces/default/libraries.html @@ -0,0 +1,151 @@ +<%inherit file="base.html"/> + +<%def name="headIncludes()"> + + + + +<%def name="body()"> +
    +
    +
    + All Libraries +
    +
    + +   + +
    +
    +
    + + + + + + + + + + + + + + + + + +
    EditLibrary NameLibrary TypeTotal Movies / TV Shows / ArtistsTotal Seasons / AlbumsTotal Episodes / TracksLast AccessedLast WatchedTotal Plays
    + +
    +
    + + + +<%def name="javascriptIncludes()"> + + + + + + + \ No newline at end of file diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 29b87aca..eafe4820 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -439,8 +439,8 @@ def dbcheck(): # library_sections table :: This table keeps record of the servers library sections c_db.execute( 'CREATE TABLE IF NOT EXISTS library_sections (id INTEGER PRIMARY KEY AUTOINCREMENT, ' - 'server_id TEXT, section_id INTEGER UNIQUE, section_name TEXT, section_type TEXT, thumb TEXT, ' - 'count INTEGER, parent_count INTEGER, child_count INTEGER, ' + 'server_id TEXT, section_id INTEGER UNIQUE, section_name TEXT, section_type TEXT, ' + 'thumb TEXT, custom_thumb_url TEXT, count INTEGER, parent_count INTEGER, child_count INTEGER, ' 'do_notify INTEGER DEFAULT 1, keep_history INTEGER DEFAULT 1)' ) diff --git a/plexpy/libraries.py b/plexpy/libraries.py new file mode 100644 index 00000000..8df0ec0e --- /dev/null +++ b/plexpy/libraries.py @@ -0,0 +1,541 @@ +# This file is part of PlexPy. +# +# PlexPy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# PlexPy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with PlexPy. If not, see . + +from plexpy import logger, datatables, common, database, helpers + + +class Libraries(object): + + def __init__(self): + pass + + def get_library_list(self, kwargs=None): + data_tables = datatables.DataTables() + + columns = ['library_sections.section_id', + 'library_sections.section_name', + 'library_sections.section_type', + 'library_sections.count as count', + 'library_sections.parent_count', + 'library_sections.child_count', + '(CASE WHEN library_sections.custom_thumb_url IS NULL THEN library_sections.thumb ELSE ' \ + 'custom_thumb_url END) AS library_thumb', + 'COUNT(session_history.id) as plays', + 'MAX(session_history.started) as last_accessed', + 'session_history_metadata.full_title as last_watched', + 'session_history_metadata.thumb', + 'session_history_metadata.parent_thumb', + 'session_history_metadata.grandparent_thumb', + 'session_history_metadata.media_type', + 'session_history.rating_key', + 'session_history_media_info.video_decision', + 'library_sections.do_notify', + 'library_sections.keep_history' + ] + try: + query = data_tables.ssp_query(table_name='library_sections', + columns=columns, + custom_where=[], + group_by=['library_sections.section_id'], + join_types=['LEFT OUTER JOIN', + 'LEFT OUTER JOIN', + 'LEFT OUTER JOIN'], + join_tables=['session_history_metadata', + 'session_history', + 'session_history_media_info'], + join_evals=[['session_history_metadata.library_id', 'library_sections.section_id'], + ['session_history_metadata.id', 'session_history.id'], + ['session_history_metadata.id', 'session_history_media_info.id']], + kwargs=kwargs) + except: + logger.warn("Unable to execute database query for get_library_list.") + return {'recordsFiltered': 0, + 'recordsTotal': 0, + 'draw': 0, + 'data': 'null', + 'error': 'Unable to execute database query.'} + + result = query['result'] + + rows = [] + for item in result: + if item['media_type'] == 'episode' and item['parent_thumb']: + thumb = item['parent_thumb'] + elif item['media_type'] == 'episode': + thumb = item['grandparent_thumb'] + else: + thumb = item['thumb'] + + row = {'plays': item['plays'], + 'last_accessed': item['last_accessed'], + 'last_watched': item['last_watched'], + 'thumb': thumb, + 'media_type': item['media_type'], + 'rating_key': item['rating_key'], + 'video_decision': item['video_decision'], + 'section_id': item['section_id'], + 'section_name': item['section_name'], + 'section_type': item['section_type'].capitalize(), + 'count': item['count'], + 'parent_count': item['parent_count'], + 'library_thumb': item['library_thumb'], + 'child_count': item['child_count'], + 'do_notify': helpers.checked(item['do_notify']), + 'keep_history': helpers.checked(item['keep_history']) + } + + rows.append(row) + + dict = {'recordsFiltered': query['filteredCount'], + 'recordsTotal': query['totalCount'], + 'data': rows, + 'draw': query['draw'] + } + + return dict + + def get_user_unique_ips(self, kwargs=None, custom_where=None): + data_tables = datatables.DataTables() + + # Change custom_where column name due to ambiguous column name after JOIN + custom_where[0][0] = 'custom_user_id' if custom_where[0][0] == 'user_id' else custom_where[0][0] + + columns = ['session_history.id', + 'session_history.started as last_seen', + 'session_history.ip_address as ip_address', + 'COUNT(session_history.id) as play_count', + 'session_history.platform as platform', + 'session_history.player as player', + 'session_history_metadata.full_title as last_watched', + 'session_history_metadata.thumb', + 'session_history_metadata.parent_thumb', + 'session_history_metadata.grandparent_thumb', + 'session_history_metadata.media_type', + 'session_history.rating_key as rating_key', + 'session_history_media_info.video_decision', + 'session_history.user as user', + 'session_history.user_id as custom_user_id', + '(case when users.friendly_name is null then users.username else \ + users.friendly_name end) as friendly_name' + ] + + try: + query = data_tables.ssp_query(table_name='session_history', + columns=columns, + custom_where=custom_where, + group_by=['ip_address'], + join_types=['JOIN', + 'JOIN', + 'JOIN'], + join_tables=['users', + 'session_history_metadata', + 'session_history_media_info'], + join_evals=[['session_history.user_id', 'users.user_id'], + ['session_history.id', 'session_history_metadata.id'], + ['session_history.id', 'session_history_media_info.id']], + kwargs=kwargs) + except: + logger.warn("Unable to execute database query.") + return {'recordsFiltered': 0, + 'recordsTotal': 0, + 'draw': 0, + 'data': 'null', + 'error': 'Unable to execute database query.'} + + results = query['result'] + + rows = [] + for item in results: + if item["media_type"] == 'episode' and item["parent_thumb"]: + thumb = item["parent_thumb"] + elif item["media_type"] == 'episode': + thumb = item["grandparent_thumb"] + else: + thumb = item["thumb"] + + # Rename Mystery platform names + platform = common.PLATFORM_NAME_OVERRIDES.get(item["platform"], item["platform"]) + + row = {"id": item['id'], + "last_seen": item['last_seen'], + "ip_address": item['ip_address'], + "play_count": item['play_count'], + "platform": platform, + "player": item['player'], + "last_watched": item['last_watched'], + "thumb": thumb, + "media_type": item['media_type'], + "rating_key": item['rating_key'], + "video_decision": item['video_decision'], + "friendly_name": item['friendly_name'] + } + + rows.append(row) + + dict = {'recordsFiltered': query['filteredCount'], + 'recordsTotal': query['totalCount'], + 'data': rows, + 'draw': query['draw'] + } + + return dict + + # TODO: The getter and setter for this needs to become a config getter/setter for more than just friendlyname + def set_library_config(self, section_id=None, do_notify=1, keep_history=1, custom_thumb=''): + if section_id: + monitor_db = database.MonitorDatabase() + + key_dict = {'section_id': section_id} + value_dict = {'do_notify': do_notify, + 'keep_history': keep_history, + 'custom_thumb_url': custom_thumb} + try: + monitor_db.upsert('library_sections', value_dict, key_dict) + except: + logger.warn("Unable to execute database query for set_user_friendly_name.") + + def set_user_profile_url(self, user=None, user_id=None, profile_url=None): + if user_id: + if profile_url.strip() == '': + profile_url = None + + monitor_db = database.MonitorDatabase() + + control_value_dict = {"user_id": user_id} + new_value_dict = {"custom_avatar_url": profile_url} + try: + monitor_db.upsert('users', new_value_dict, control_value_dict) + except Exception, e: + logger.debug(u"Uncaught exception %s" % e) + if user: + if profile_url.strip() == '': + profile_url = None + + monitor_db = database.MonitorDatabase() + + control_value_dict = {"username": user} + new_value_dict = {"custom_avatar_url": profile_url} + try: + monitor_db.upsert('users', new_value_dict, control_value_dict) + except Exception, e: + logger.debug(u"Uncaught exception %s" % e) + + def get_user_friendly_name(self, user=None, user_id=None): + if user_id: + monitor_db = database.MonitorDatabase() + query = 'select username, ' \ + '(CASE WHEN friendly_name IS NULL THEN username ELSE friendly_name END) as friendly_name,' \ + 'do_notify, keep_history, custom_avatar_url as thumb ' \ + 'FROM users WHERE user_id = ?' + result = monitor_db.select(query, args=[user_id]) + if result: + user_detail = {'user_id': user_id, + 'user': result[0]['username'], + 'friendly_name': result[0]['friendly_name'], + 'thumb': result[0]['thumb'], + 'do_notify': helpers.checked(result[0]['do_notify']), + 'keep_history': helpers.checked(result[0]['keep_history']) + } + return user_detail + else: + user_detail = {'user_id': user_id, + 'user': '', + 'friendly_name': '', + 'do_notify': '', + 'thumb': '', + 'keep_history': ''} + return user_detail + elif user: + monitor_db = database.MonitorDatabase() + query = 'select user_id, ' \ + '(CASE WHEN friendly_name IS NULL THEN username ELSE friendly_name END) as friendly_name,' \ + 'do_notify, keep_history, custom_avatar_url as thumb ' \ + 'FROM users WHERE username = ?' + result = monitor_db.select(query, args=[user]) + if result: + user_detail = {'user_id': result[0]['user_id'], + 'user': user, + 'friendly_name': result[0]['friendly_name'], + 'thumb': result[0]['thumb'], + 'do_notify': helpers.checked(result[0]['do_notify']), + 'keep_history': helpers.checked(result[0]['keep_history'])} + return user_detail + else: + user_detail = {'user_id': None, + 'user': user, + 'friendly_name': '', + 'do_notify': '', + 'thumb': '', + 'keep_history': ''} + return user_detail + + return None + + def get_user_id(self, user=None): + if user: + try: + monitor_db = database.MonitorDatabase() + query = 'select user_id FROM users WHERE username = ?' + result = monitor_db.select_single(query, args=[user]) + if result: + return result + else: + return None + except: + return None + + return None + + def get_user_details(self, user=None, user_id=None): + from plexpy import plextv + + monitor_db = database.MonitorDatabase() + + if user: + query = 'SELECT user_id, username, friendly_name, email, ' \ + 'custom_avatar_url as thumb, is_home_user, is_allow_sync, is_restricted, do_notify ' \ + 'FROM users ' \ + 'WHERE username = ? ' \ + 'UNION ALL ' \ + 'SELECT null, user, null, null, null, null, null, null, null ' \ + 'FROM session_history ' \ + 'WHERE user = ? ' \ + 'GROUP BY user ' \ + 'LIMIT 1' + result = monitor_db.select(query, args=[user, user]) + elif user_id: + query = 'SELECT user_id, username, friendly_name, email, ' \ + 'custom_avatar_url as thumb, is_home_user, is_allow_sync, is_restricted, do_notify ' \ + 'FROM users ' \ + 'WHERE user_id = ? ' \ + 'UNION ALL ' \ + 'SELECT user_id, user, null, null, null, null, null, null, null ' \ + 'FROM session_history ' \ + 'WHERE user_id = ? ' \ + 'GROUP BY user ' \ + 'LIMIT 1' + result = monitor_db.select(query, args=[user_id, user_id]) + else: + result = None + + if result: + user_details = {} + for item in result: + if not item['friendly_name']: + friendly_name = item['username'] + else: + friendly_name = item['friendly_name'] + if not item['thumb'] or item['thumb'] == '': + user_thumb = common.DEFAULT_USER_THUMB + else: + user_thumb = item['thumb'] + + user_details = {"user_id": item['user_id'], + "username": item['username'], + "friendly_name": friendly_name, + "email": item['email'], + "thumb": user_thumb, + "is_home_user": item['is_home_user'], + "is_allow_sync": item['is_allow_sync'], + "is_restricted": item['is_restricted'], + "do_notify": item['do_notify'] + } + return user_details + else: + logger.warn(u"PlexPy :: Unable to retrieve user from local database. Requesting user list refresh.") + # Let's first refresh the user list to make sure the user isn't newly added and not in the db yet + if user: + # Refresh users + plextv.refresh_users() + query = 'SELECT user_id, username, friendly_name, email, ' \ + 'custom_avatar_url as thumb, is_home_user, is_allow_sync, is_restricted, do_notify ' \ + 'FROM users ' \ + 'WHERE username = ? ' \ + 'UNION ALL ' \ + 'SELECT null, user, null, null, null, null, null, null, null ' \ + 'FROM session_history ' \ + 'WHERE user = ? ' \ + 'GROUP BY user ' \ + 'LIMIT 1' + result = monitor_db.select(query, args=[user, user]) + elif user_id: + # Refresh users + plextv.refresh_users() + query = 'SELECT user_id, username, friendly_name, email, ' \ + 'custom_avatar_url as thumb, is_home_user, is_allow_sync, is_restricted, do_notify ' \ + 'FROM users ' \ + 'WHERE user_id = ? ' \ + 'UNION ALL ' \ + 'SELECT user_id, user, null, null, null, null, null, null, null ' \ + 'FROM session_history ' \ + 'WHERE user_id = ? ' \ + 'GROUP BY user ' \ + 'LIMIT 1' + result = monitor_db.select(query, args=[user_id, user_id]) + else: + result = None + + if result: + user_details = {} + for item in result: + if not item['friendly_name']: + friendly_name = item['username'] + else: + friendly_name = item['friendly_name'] + if not item['thumb'] or item['thumb'] == '': + user_thumb = common.DEFAULT_USER_THUMB + else: + user_thumb = item['thumb'] + + user_details = {"user_id": item['user_id'], + "username": item['username'], + "friendly_name": friendly_name, + "email": item['email'], + "thumb": user_thumb, + "is_home_user": item['is_home_user'], + "is_allow_sync": item['is_allow_sync'], + "is_restricted": item['is_restricted'], + "do_notify": item['do_notify'] + } + return user_details + else: + # If there is no user data we must return something + # Use "Local" user to retain compatibility with PlexWatch database value + return {"user_id": None, + "username": 'Local', + "friendly_name": 'Local', + "email": '', + "thumb": '', + "is_home_user": 0, + "is_allow_sync": 0, + "is_restricted": 0, + "do_notify": 0 + } + + def get_user_watch_time_stats(self, user=None, user_id=None): + monitor_db = database.MonitorDatabase() + + time_queries = [1, 7, 30, 0] + user_watch_time_stats = [] + + for days in time_queries: + if days > 0: + if user_id: + query = 'SELECT (SUM(stopped - started) - ' \ + 'SUM(CASE WHEN paused_counter is null THEN 0 ELSE paused_counter END)) as total_time, ' \ + 'COUNT(id) AS total_plays ' \ + 'FROM session_history ' \ + 'WHERE datetime(stopped, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") ' \ + 'AND user_id = ?' % days + result = monitor_db.select(query, args=[user_id]) + elif user: + query = 'SELECT (SUM(stopped - started) - ' \ + 'SUM(CASE WHEN paused_counter is null THEN 0 ELSE paused_counter END)) as total_time, ' \ + 'COUNT(id) AS total_plays ' \ + 'FROM session_history ' \ + 'WHERE datetime(stopped, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") ' \ + 'AND user = ?' % days + result = monitor_db.select(query, args=[user]) + else: + query = 'SELECT (SUM(stopped - started) - ' \ + 'SUM(CASE WHEN paused_counter is null THEN 0 ELSE paused_counter END)) as total_time, ' \ + 'COUNT(id) AS total_plays ' \ + 'FROM session_history ' \ + 'WHERE user = ?' + result = monitor_db.select(query, args=[user]) + + for item in result: + if item['total_time']: + total_time = item['total_time'] + total_plays = item['total_plays'] + else: + total_time = 0 + total_plays = 0 + + row = {'query_days': days, + 'total_time': total_time, + 'total_plays': total_plays + } + + user_watch_time_stats.append(row) + + return user_watch_time_stats + + def get_user_player_stats(self, user=None, user_id=None): + monitor_db = database.MonitorDatabase() + + player_stats = [] + result_id = 0 + + try: + if user_id: + query = 'SELECT player, COUNT(player) as player_count, platform ' \ + 'FROM session_history ' \ + 'WHERE user_id = ? ' \ + 'GROUP BY player ' \ + 'ORDER BY player_count DESC' + result = monitor_db.select(query, args=[user_id]) + else: + query = 'SELECT player, COUNT(player) as player_count, platform ' \ + 'FROM session_history ' \ + 'WHERE user = ? ' \ + 'GROUP BY player ' \ + 'ORDER BY player_count DESC' + result = monitor_db.select(query, args=[user]) + except: + logger.warn("Unable to execute database query.") + return None + + for item in result: + # Rename Mystery platform names + platform_type = common.PLATFORM_NAME_OVERRIDES.get(item['platform'], item['platform']) + + row = {'player_name': item['player'], + 'platform_type': platform_type, + 'total_plays': item['player_count'], + 'result_id': result_id + } + player_stats.append(row) + result_id += 1 + + return player_stats + + def delete_all_library_history(self, library_id=None): + monitor_db = database.MonitorDatabase() + + if library_id.isdigit(): + logger.info(u"PlexPy Libraries :: Deleting all history for library id %s from database." % library_id) + session_history_media_info_del = \ + monitor_db.action('DELETE FROM ' + 'session_history_media_info ' + 'WHERE session_history_media_info.id IN (SELECT session_history_media_info.id ' + 'FROM session_history_media_info ' + 'JOIN session_history_metadata ON session_history_media_info.id = session_history_metadata.id ' + 'WHERE session_history_metadata.library_id = ?)', [library_id]) + session_history_del = \ + monitor_db.action('DELETE FROM ' + 'session_history ' + 'WHERE session_history.id IN (SELECT session_history.id ' + 'FROM session_history ' + 'JOIN session_history_metadata ON session_history.id = session_history_metadata.id ' + 'WHERE session_history_metadata.library_id = ?)', [library_id]) + session_history_metadata_del = \ + monitor_db.action('DELETE FROM ' + 'session_history_metadata ' + 'WHERE session_history_metadata.library_id = ?', [library_id]) + + return 'Deleted all items for library_id %s.' % library_id + else: + return 'Unable to delete items. Input library_id not valid.' diff --git a/plexpy/webserve.py b/plexpy/webserve.py index eb7145b5..5970e3c3 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -13,7 +13,7 @@ # You should have received a copy of the GNU General Public License # along with PlexPy. If not, see . -from plexpy import logger, notifiers, plextv, pmsconnect, common, log_reader, datafactory, graphs, users, helpers +from plexpy import logger, notifiers, plextv, pmsconnect, common, log_reader, datafactory, graphs, users, libraries from plexpy.helpers import checked, radio from mako.lookup import TemplateLookup @@ -167,6 +167,10 @@ class WebInterface(object): def users(self): return serve_template(templatename="users.html", title="Users") + @cherrypy.expose + def libraries(self): + return serve_template(templatename="libraries.html", title="Libraries") + @cherrypy.expose def graphs(self): @@ -262,6 +266,26 @@ class WebInterface(object): status_message = "Failed to update user." return status_message + @cherrypy.expose + def edit_library(self, section_id=None, **kwargs): + do_notify = kwargs.get('do_notify', 0) + keep_history = kwargs.get('keep_history', 0) + custom_thumb = kwargs.get('custom_thumb', '') + + library_data = libraries.Libraries() + if section_id: + try: + library_data.set_library_config(section_id=section_id, + do_notify=do_notify, + keep_history=keep_history, + custom_thumb=custom_thumb) + + status_message = "Successfully updated library." + return status_message + except: + status_message = "Failed to update library." + return status_message + @cherrypy.expose def get_stream_data(self, row_id=None, user=None, **kwargs): @@ -290,6 +314,15 @@ class WebInterface(object): cherrypy.response.headers['Content-type'] = 'application/json' return json.dumps(user_list) + @cherrypy.expose + def get_library_list(self, **kwargs): + + library_data = libraries.Libraries() + library_list = library_data.get_library_list(kwargs=kwargs) + + cherrypy.response.headers['Content-type'] = 'application/json' + return json.dumps(library_list) + @cherrypy.expose def checkGithub(self): from plexpy import versioncheck @@ -1560,6 +1593,20 @@ class WebInterface(object): cherrypy.response.headers['Content-type'] = 'application/json' return json.dumps({'message': 'no data received'}) + @cherrypy.expose + def delete_all_library_history(self, library_id, **kwargs): + library_data = libraries.Libraries() + + if library_id: + delete_row = library_data.delete_all_library_history(library_id=library_id) + + if delete_row: + cherrypy.response.headers['Content-type'] = 'application/json' + return json.dumps({'message': delete_row}) + else: + cherrypy.response.headers['Content-type'] = 'application/json' + return json.dumps({'message': 'no data received'}) + @cherrypy.expose def search(self, query=''):