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:
' +
+ '
   ' +
+ '
 ' +
+ '
 ');
+ },
+ "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('
');
+ } 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('
');
+ } else if (rowData['media_type'] === 'episode') {
+ media_type = '
';
+ thumb_popover = '
' + cellData + ''
+ $(td).html('
');
+ } else if (rowData['media_type'] === 'track') {
+ media_type = '
';
+ thumb_popover = '
' + cellData + ''
+ $(td).html('
');
+ } 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>
+
+<%def name="body()">
+
+
+
+
+
+
+ Edit |
+ |
+ Library Name |
+ Library Type |
+ Total Movies / TV Shows / Artists |
+ Total Seasons / Albums |
+ Total Episodes / Tracks |
+ Last Accessed |
+ Last Watched |
+ Total Plays |
+
+
+
+
+
+
+
+
+
+
+
+
This is permanent and cannot be undone!
+
+
+
+
+
+
+
+
+%def>
+
+<%def name="javascriptIncludes()">
+
+
+
+
+
+
+%def>
\ 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=''):