Add table to list exported items

This commit is contained in:
JonnyWong16 2020-08-03 13:24:16 -07:00
parent c102020698
commit 5468676811
No known key found for this signature in database
GPG key ID: B1F1F9807184697A
6 changed files with 357 additions and 0 deletions

View file

@ -473,6 +473,21 @@ fieldset[disabled] .btn-bright.active {
background-color: #ac2925; background-color: #ac2925;
border-color: #761c19; border-color: #761c19;
} }
.btn-dark.btn-download:hover {
color: #fff;
background-color: #449d44;
border-color: #398439;
}
.btn-dark.btn-download.active {
color: #fff;
background-color: #449d44;
border-color: #398439;
}
.btn-dark.btn-download.active:hover {
color: #fff;
background-color: #398439;
border-color: #255625;
}
.btn-group select { .btn-group select {
margin-top: 0; margin-top: 0;
} }

View file

@ -0,0 +1,100 @@
var date_format = 'YYYY-MM-DD';
var time_format = 'hh:mm a';
$.ajax({
url: 'get_date_formats',
type: 'GET',
success: function (data) {
date_format = data.date_format;
time_format = data.time_format;
}
});
export_table_options = {
"destroy": true,
"language": {
"search": "Search: ",
"lengthMenu": "Show _MENU_ entries per page",
"info": "Showing _START_ to _END_ of _TOTAL_ library items",
"infoEmpty": "Showing 0 to 0 of 0 entries",
"infoFiltered": "<span class='hidden-md hidden-sm hidden-xs'>(filtered from _MAX_ total entries)</span>",
"emptyTable": "No data in table",
"loadingRecords": '<i class="fa fa-refresh fa-spin"></i> Loading items...</div>'
},
"pagingType": "full_numbers",
"stateSave": true,
"stateDuration": 0,
"processing": false,
"serverSide": true,
"pageLength": 25,
"order": [0, 'desc'],
"autoWidth": false,
"scrollX": true,
"columnDefs": [
{
"targets": [0],
"data": "timestamp",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
$(td).html(moment(cellData, "X").format(date_format + ' ' + time_format));
}
},
"width": "10%",
"className": "no-wrap"
},
{
"targets": [1],
"data": "media_type_title",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
$(td).html(cellData);
}
},
"width": "10%",
"className": "no-wrap"
},
{
"targets": [2],
"data": "rating_key",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
$(td).html(cellData);
}
},
"width": "10%",
"className": "no-wrap"
},
{
"targets": [3],
"data": "filename",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
$(td).html(cellData);
}
},
"width": "60%",
"className": "no-wrap"
},
{
"targets": [4],
"data": "complete",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData === 1) {
$(td).html('<button class="btn btn-xs btn-dark btn-download" data-id="' + rowData['row_id'] + '"><i class="fa fa-file-download fa-fw"></i> Download</button>');
} else {
$(td).html('<button class="btn btn-xs btn-dark" data-id="' + rowData['row_id'] + '" disabled><i class="fa fa-spinner fa-spin fa-fw"></i> Processing</button>');
}
},
"width": "10%"
}
],
"drawCallback": function (settings) {
// Jump to top of page
//$('html,body').scrollTop(0);
$('#ajaxMsg').fadeOut();
},
"preDrawCallback": function(settings) {
var msg = "<i class='fa fa-refresh fa-spin'></i>&nbsp; Fetching rows...";
showMsg(msg, false, false, 0)
}
};

View file

@ -93,6 +93,7 @@ DOCUMENTATION :: END
% if _session['user_group'] == 'admin': % if _session['user_group'] == 'admin':
% if data['section_id'] != LIVE_TV_SECTION_ID: % if data['section_id'] != LIVE_TV_SECTION_ID:
<li><a id="media-info-tab-btn" href="#tabs-mediainfo" role="tab" data-toggle="tab">Media Info</a></li> <li><a id="media-info-tab-btn" href="#tabs-mediainfo" role="tab" data-toggle="tab">Media Info</a></li>
<li><a id="export-tab-btn" href="#tabs-export" role="tab" data-toggle="tab">Export</a></li>
% endif % endif
% endif % endif
</ul> </ul>
@ -305,6 +306,47 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
</div> </div>
<div role="tabpanel" class="tab-pane" id="tabs-export">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class='table-card-header'>
<div class="header-bar">
<span>
<i class="fa fa-file-export"></i> Exports for <strong>
<span class="set-username">${data['section_name']}</span>
</strong>
</span>
</div>
<div class="button-bar">
% if _session['user_group'] == 'admin':
<div class="btn-group">
<button class="btn btn-dark refresh-export-table-button" id="refresh-export-table">
<i class="fa fa-refresh"></i> Refresh exports
</button>
</div>
% endif
<div class="btn-group colvis-button-bar" id="button-bar-export"></div>
</div>
</div>
<div class="table-card-back">
<table class="display export_table" id="export_table-SID-${data['section_id']}" width="100%">
<thead>
<tr>
<th align="left" id="timestamp">Exported At</th>
<th align="left" id="media_type_title">Media Type</th>
<th align="left" id="rating_key">Rating Key</th>
<th align="left" id="filename">Filename</th>
<th align="left" id="complete">Download</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -387,6 +429,7 @@ DOCUMENTATION :: END
<script src="${http_root}js/moment-with-locale.js"></script> <script src="${http_root}js/moment-with-locale.js"></script>
<script src="${http_root}js/tables/history_table.js${cache_param}"></script> <script src="${http_root}js/tables/history_table.js${cache_param}"></script>
<script src="${http_root}js/tables/media_info_table.js${cache_param}"></script> <script src="${http_root}js/tables/media_info_table.js${cache_param}"></script>
<script src="${http_root}js/tables/export_table.js${cache_param}"></script>
<script> <script>
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) { $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
$.fn.dataTable.tables({ visible: true, api: true }).columns.adjust(); $.fn.dataTable.tables({ visible: true, api: true }).columns.adjust();
@ -461,6 +504,36 @@ DOCUMENTATION :: END
refresh_table = false; refresh_table = false;
}); });
function loadExportTable() {
// Build export table
export_table_options.ajax = {
url: 'get_library_export',
type: 'POST',
data: function ( d ) {
return {
json_data: JSON.stringify( d ),
section_id: section_id
};
}
};
export_table = $('#export_table-SID-${data["section_id"]}').DataTable(export_table_options);
var colvis = new $.fn.dataTable.ColVis(export_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' });
$(colvis.button()).appendTo('#button-bar-export-table');
clearSearchButton('export_table-SID-${data["section_id"]}', export_table);
}
$('a[href="#tabs-export"]').on('shown.bs.tab', function() {
if (typeof(export_table) === 'undefined') {
loadExportTable();
}
});
$("#refresh-export-table").click(function () {
export_table.draw();
});
$("#edit-library-tooltip").tooltip(); $("#edit-library-tooltip").tooltip();
// Load edit library modal // Load edit library modal

View file

@ -788,6 +788,13 @@ def dbcheck():
'img_hash TEXT, cloudinary_title TEXT, cloudinary_url TEXT)' 'img_hash TEXT, cloudinary_title TEXT, cloudinary_url TEXT)'
) )
# exports table :: This table keeps record of the exported files
c_db.execute(
'CREATE TABLE IF NOT EXISTS exports (id INTEGER PRIMARY KEY AUTOINCREMENT, '
'timestamp INTEGER, section_id INTEGER, rating_key INTEGER, media_type TEXT, '
'filename TEXT, complete INTEGER DEFAULT 0)'
)
# Upgrade sessions table from earlier versions # Upgrade sessions table from earlier versions
try: try:
c_db.execute('SELECT started FROM sessions') c_db.execute('SELECT started FROM sessions')

View file

@ -27,10 +27,14 @@ from multiprocessing.dummy import Pool as ThreadPool
import plexpy import plexpy
if plexpy.PYTHON2: if plexpy.PYTHON2:
import database
import datatables
import helpers import helpers
import logger import logger
from plex import Plex from plex import Plex
else: else:
from plexpy import database
from plexpy import datatables
from plexpy import helpers from plexpy import helpers
from plexpy import logger from plexpy import logger
from plexpy.plex import Plex from plexpy.plex import Plex
@ -874,6 +878,7 @@ def export(section_id=None, rating_key=None, output_format='json'):
item = plex.get_item(helpers.cast_to_int(rating_key)) item = plex.get_item(helpers.cast_to_int(rating_key))
media_type = item.type media_type = item.type
section_id = item.librarySectionID
if media_type in ('season', 'episode', 'album', 'track'): if media_type in ('season', 'episode', 'album', 'track'):
item_title = item._defaultSyncTitle() item_title = item._defaultSyncTitle()
@ -895,6 +900,15 @@ def export(section_id=None, rating_key=None, output_format='json'):
filepath = os.path.join(plexpy.CONFIG.CACHE_DIR, filename) filepath = os.path.join(plexpy.CONFIG.CACHE_DIR, filename)
logger.info("Tautulli Exporter :: Starting export for '%s'...", filename) logger.info("Tautulli Exporter :: Starting export for '%s'...", filename)
export_id = set_export_state(timestamp=timestamp,
section_id=section_id,
rating_key=rating_key,
media_type=media_type,
filename=filename)
if not export_id:
logger.error("Tautulli Exporter :: Failed to export '%s'", filename)
return
attrs = MEDIA_TYPES[media_type] attrs = MEDIA_TYPES[media_type]
part = partial(helpers.get_attrs_to_dict, attrs=attrs) part = partial(helpers.get_attrs_to_dict, attrs=attrs)
@ -913,4 +927,93 @@ def export(section_id=None, rating_key=None, output_format='json'):
writer.writeheader() writer.writeheader()
writer.writerows(flatten_result) writer.writerows(flatten_result)
set_export_complete(export_id=export_id)
logger.info("Tautulli Exporter :: Successfully exported to '%s'", filepath) logger.info("Tautulli Exporter :: Successfully exported to '%s'", filepath)
def set_export_state(timestamp, section_id, rating_key, media_type, filename):
keys = {'timestamp': timestamp,
'section_id': section_id,
'rating_key': rating_key,
'media_type': media_type}
values = {'filename': filename}
db = database.MonitorDatabase()
try:
db.upsert(table_name='exports', key_dict=keys, value_dict=values)
return db.last_insert_id()
except Exception as e:
logger.error("Tautulli Exporter :: Unable to save export to database: %s", e)
return False
def set_export_complete(export_id):
keys = {'id': export_id}
values = {'complete': 1}
db = database.MonitorDatabase()
db.upsert(table_name='exports', key_dict=keys, value_dict=values)
def get_export_datatable(section_id=None, rating_key=None, kwargs=None):
default_return = {'recordsFiltered': 0,
'recordsTotal': 0,
'draw': 0,
'data': 'null',
'error': 'Unable to execute database query.'}
data_tables = datatables.DataTables()
custom_where = []
if section_id:
custom_where.append(['exports.section_id', section_id])
if rating_key:
custom_where.append(['exports.rating_key', rating_key])
columns = ['exports.id AS row_id',
'exports.timestamp',
'exports.section_id',
'exports.rating_key',
'exports.media_type',
'exports.filename',
'exports.complete'
]
try:
query = data_tables.ssp_query(table_name='exports',
columns=columns,
custom_where=custom_where,
group_by=[],
join_types=[],
join_tables=[],
join_evals=[],
kwargs=kwargs)
except Exception as e:
logger.warn("Tautulli Exporter :: Unable to execute database query for get_export_datatable: %s." % e)
return default_return
result = query['result']
rows = []
for item in result:
media_type_title = item['media_type'].title()
row = {'row_id': item['row_id'],
'timestamp': item['timestamp'],
'section_id': item['section_id'],
'rating_key': item['rating_key'],
'media_type': item['media_type'],
'media_type_title': media_type_title,
'filename': item['filename'],
'complete': item['complete']
}
rows.append(row)
result = {'recordsFiltered': query['filteredCount'],
'recordsTotal': query['totalCount'],
'data': rows,
'draw': query['draw']
}
return result

View file

@ -870,6 +870,65 @@ class WebInterface(object):
return {'success': result} return {'success': result}
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
@addtoapi()
def get_library_export(self, section_id=None, rating_key=None, **kwargs):
""" Get the data on the Tautulli export tables.
```
Required parameters:
section_id (str): The id of the Plex library section, OR
rating_key (str): The rating key of the exported item
Optional parameters:
order_column (str): "added_at", "sort_title", "container", "bitrate", "video_codec",
"video_resolution", "video_framerate", "audio_codec", "audio_channels",
"file_size", "last_played", "play_count"
order_dir (str): "desc" or "asc"
start (int): Row to start from, 0
length (int): Number of items to return, 25
search (str): A string to search for, "Thrones"
Returns:
json:
{"draw": 1,
"recordsTotal": 10,
"recordsFiltered": 3,
"data":
[{"row_id": 2,
"timestamp": 1596484600,
"section_id": 1,
"rating_key": 270716,
"media_type": "movie",
"media_type_title": "Movie",
"filename": "Movie - Frozen II [270716].20200803125640.json",
"complete": 1
},
{...},
{...}
]
}
```
"""
# Check if datatables json_data was received.
# If not, then build the minimal amount of json data for a query
if not kwargs.get('json_data'):
# TODO: Find some one way to automatically get the columns
dt_columns = [("timestamp", True, False),
("media_type_title", True, True),
("rating_key", True, True),
("filename", True, True),
("complete", True, False)]
kwargs['json_data'] = build_datatables_json(kwargs, dt_columns, "timestamp")
result = exporter.get_export_datatable(section_id=section_id,
rating_key=rating_key,
kwargs=kwargs)
return result
@cherrypy.expose @cherrypy.expose
@cherrypy.tools.json_out() @cherrypy.tools.json_out()
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))