Add option to upload or browse for a database file to import

This commit is contained in:
JonnyWong16 2020-05-03 14:33:25 -07:00
parent a869859491
commit 980c4f7618
No known key found for this signature in database
GPG key ID: B1F1F9807184697A
6 changed files with 316 additions and 79 deletions

View file

@ -5,6 +5,7 @@
<h4 class="modal-title">Import ${app} Database</h4>
</div>
<div class="modal-body" id="modal-text">
<form id="import_database" enctype="multipart/form-data" method="post" name="import_database">
<input type="hidden" id="import_app" name="import_app" value="${app.lower()}" />
% if app in ('PlexWatch', 'Plexivity'):
<p class="help-block">
@ -19,13 +20,33 @@
</p>
% endif
<div class="form-group">
<label for="import_database_path">Database Location</label>
<label for="import_database_file">Option 1: Upload a Database File</label>
<div class="row">
<div class="col-xs-12">
<input type="text" class="form-control" id="import_database_path" name="import_database_path" value="" required>
<div class="input-group">
<label for="import_database_file" class="input-group-btn">
<span class="btn btn-form">Upload</span>
<input type="file" style="display: none;" id="import_database_file" name="import_database_file" required>
</label>
<input type="text" class="form-control" disabled>
</div>
</div>
<p class="help-block">Enter the full path to the ${app} database you wish to import.</p>
</div>
<p class="help-block">Upload the ${app} database you wish to import.</p>
</div>
<div class="form-group">
<label for="import_database_path">Option 2: Browse for a Database File</label>
<div class="row">
<div class="col-xs-12">
<div class="input-group">
<span class="input-group-btn">
<button class="btn btn-form" type="button" id="import_database_path_browse">Browse</button>
</span>
<input type="text" class="form-control" id="import_database_path" name="import_database_path" value="" required disabled>
</div>
</div>
</div>
<p class="help-block">Browse for the ${app} database you wish to import.</p>
</div>
% if app == 'Tautulli':
<div class="form-group">
@ -34,7 +55,7 @@
<div class="col-xs-4">
<select class="form-control" id="import_method" name="import_method">
<option value="merge">Merge</option>
<option value="overwrite">Oerwrite</option>
<option value="overwrite">Overwrite</option>
</select>
</div>
</div>
@ -74,6 +95,7 @@
<p class="help-block">Enter the minimum duration (in seconds) an item must have been active for. Set to 0 to import all.</p>
</div>
% endif
</form>
</div>
<div class="modal-footer">
<div>
@ -84,29 +106,61 @@
</div>
</div>
<script>
// Send database path to import script
$('#import_database_path_browse').click(function () {
$('#browse-path-type').text('Databse File');
$('#browse-path-modal').modal('show');
browsePath(null, null, '.db');
});
$('#select-browse-file').click(function () {
$('#browse-path-modal').modal('hide');
$("#import_database_path").val($('#browse-path').val());
});
$('#import_database_file').change(function() {
if ($(this)[0].files[0]) {
var input = $(this).parents('.input-group').find(':text')
input.val($(this)[0].files[0].name);
}
});
$("#import_db").click(function() {
var import_app = $("#import_app").val();
var database_path = $("#import_database_path").val();
var import_method = $("#import_method").val();
var import_backup_db = $("#import_backup_db").is(':checked');
var import_table_name = $("#import_table_name").val();
var import_ignore_interval = $("#import_ignore_interval").val();
var database_file = false;
var formData = new FormData();
formData.append('app', $("#import_app").val());
formData.append('database_path', $("#import_database_path").val());
if ($('#import_database_file')[0].files[0]) {
database_file = true;
formData.append('database_file', $('#import_database_file')[0].files[0]);
}
if ($("#import_method").length) {
formData.append('method', $("#import_method").val());
}
if ($("#import_backup_db").length) {
formData.append('backup', $("#import_backup_db").is(':checked'));
}
if ($("#import_table_name").length) {
formData.append('table_name', $("#import_table_name").val());
}
if ($("#import_ignore_interval").length) {
formData.append('ignore_interval', $("#import_ignore_interval").val());
}
if (database_file) {
$("#status-message").html('<i class="fa fa-fw fa-spin fa-refresh"></i> Uploading database file...');
}
$.ajax({
url: 'import_database',
data: {
app: import_app,
database_path: database_path,
method: import_method,
backup: import_backup_db,
table_name: import_table_name,
import_ignore_interval: import_ignore_interval
},
type: 'POST',
data: formData,
cache: false,
async: true,
contentType: false,
processData: false,
success: function(data) {
$("#status-message").html(data);
$("#import_database_path").val('')
$("#import_database_file").val(null);
$("#import_database_path").val('');
}
});
});

View file

@ -4297,3 +4297,7 @@ a[data-tab-destination] {
margin-top: 0;
color: #737373;
}
#browse-path-list > li > span > i.fa {
color: #999;
}

View file

@ -237,6 +237,27 @@ function doAjaxCall(url, elem, reload, form, showMsg, callback) {
});
}
getBrowsePath = function (key, path, filter_ext) {
var deferred = $.Deferred();
$.ajax({
url: 'browse_path',
type: 'GET',
data: {
key: key,
path: path,
filter_ext: filter_ext
},
success: function(data) {
deferred.resolve(data);
},
error: function() {
deferred.reject();
}
});
return deferred;
};
function doSimpleAjaxCall(url) {
$.ajax(url);
}

View file

@ -1882,6 +1882,38 @@ Rating: {rating}/10 --> Rating: /10
</div>
</div>
<div id="mobile-device-config-modal" class="modal fade wide" tabindex="-1" role="dialog" aria-labelledby="mobile-device-config-modal"></div>
<div id="browse-path-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="browse-path-modal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
<h4 class="modal-title">File Browser</h4>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-12">
<div class="form-group">
<label for="browse-path">Select a <span id="browse-path-type"></span> Below</label>
<div class="row">
<div class="col-md-12">
<input type="text" class="form-control" id="browse-path" name="browse-path" value="" size="30" disabled>
</div>
</div>
</div>
</div>
<div class="col-md-12" style="height: 400px; overflow: auto;">
<ul id="browse-path-list" class="stacked-configs list-unstyled">
</ul>
</div>
</div>
</div>
<div class="modal-footer">
<span id="browse-path-status-message" style="padding-right: 25px;"></span>
<input type="button" id="select-browse-file" class="btn btn-bright" value="Select">
</div>
</div>
</div>
</div>
</%def>
<%def name="javascriptIncludes()">
@ -1988,6 +2020,38 @@ Rating: {rating}/10 --> Rating: /10
});
}
function browsePath(key, path, filter_ext) {
$("#browse-path-status-message").html('<i class="fa fa-fw fa-spin fa-refresh"></i>');
getBrowsePath(key, path, filter_ext).then(function (data) {
if (data.result === 'error') {
$("#browse-path-status-message").html("<i class='fa fa-exclamation-triangle'></i> " + data.message);
} else {
$("#browse-path-status-message").html("");
$('#browse-path').val(data.path);
var browse_list = $('#browse-path-list');
browse_list.parent().animate({ scrollTop: 0 }, 0);
browse_list.empty();
$.each(data.data, function(i, item) {
var browse_item = $('<li/>')
.html("<span><i class='fa fa-fw fa-" + item.icon + "'></i>&nbsp; " + item.title + "</span>")
.addClass(item.type + ' pointer')
.data('key', item.key)
.data('path', item.path)
.appendTo(browse_list)
});
$('#browse-path-list li').click(function (){
$('#browse-path').val($(this).data('path'));
if ($(this).hasClass('folder')) {
browsePath($(this).data('key'), null, filter_ext)
}
});
}
});
}
$(document).ready(function() {
// Javascript to enable link to tab

View file

@ -42,6 +42,7 @@ import os
import re
import shlex
import socket
import string
import sys
import time
import unicodedata
@ -1197,3 +1198,64 @@ def user_page(user_id=None, user=None):
params['user'] = user
return params
def browse_path(path=None, include_hidden=False, filter_ext=''):
output = []
if not os.path.isdir(path):
return output
if path != os.path.dirname(path):
parent_path = os.path.dirname(path)
out = {
'key': base64.b64encode(parent_path.encode('UTF-8')),
'path': parent_path,
'title': '..',
'type': 'folder',
'icon': 'level-up-alt'
}
output.append(out)
elif os.name == 'nt':
drives = ['%s:\\' % d for d in string.ascii_uppercase if os.path.exists('%s:' % d)]
for drive in drives:
out = {
'key': base64.b64encode(drive.encode('UTF-8')),
'path': drive,
'title': drive,
'type': 'folder',
'icon': 'level-up-alt'
}
output.append(out)
for root, dirs, files in os.walk(path):
for d in dirs:
if not include_hidden and d.startswith('.'):
continue
dir_path = os.path.join(root, d)
out = {
'key': base64.b64encode(dir_path.encode('UTF-8')),
'path': dir_path,
'title': d,
'type': 'folder',
'icon': 'folder'
}
output.append(out)
for f in files:
if not include_hidden and f.startswith('.'):
continue
if filter_ext and not f.endswith(filter_ext):
continue
file_path = os.path.join(root, f)
out = {
'key': base64.b64encode(file_path.encode('UTF-8')),
'path': file_path,
'title': f,
'type': 'file',
'icon': 'file'
}
output.append(out)
break
return output

View file

@ -21,6 +21,7 @@ from future.builtins import object
from future.builtins import str
from io import open
import base64
import json
import linecache
import os
@ -3740,13 +3741,15 @@ class WebInterface(object):
@cherrypy.expose
@requireAuth(member_of("admin"))
@addtoapi()
def import_database(self, app=None, database_path=None, method=None, backup=True,
def import_database(self, app=None, database_file=None, database_path=None, method=None, backup=True,
table_name=None, import_ignore_interval=0, **kwargs):
""" Import a Tautulli, PlexWatch, or Plexivity database into Tautulli.
```
Required parameters:
app (str): "tautulli" or "plexwatch" or "plexivity"
database_file (file): The database file to import (multipart/form-data)
or
database_path (str): The full path to the plexwatch database file
method (str): For Tautulli only, "merge" or "overwrite"
table_name (str): For PlexWatch or Plexivity only, "processed" or "grouped"
@ -3765,6 +3768,20 @@ class WebInterface(object):
if not app:
return 'No app specified for import'
if database_file:
database_path = os.path.join(plexpy.CONFIG.CACHE_DIR, database_file.filename)
logger.info("Received database file '%s' for import. Saving to cache '%s'.",
database_file.filename, database_path)
with open(database_path, 'wb') as f:
while True:
data = database_file.file.read(8192)
if not data:
break
f.write(data)
if not database_path:
return 'No database specified for import'
if app.lower() == 'tautulli':
db_check_msg = database.validate_database(database=database_path)
if db_check_msg == 'success':
@ -3816,6 +3833,21 @@ class WebInterface(object):
logger.warn("No app specified for import.")
return
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
def browse_path(self, key=None, path=None, filter_ext=''):
if key:
path = base64.b64decode(key)
if not path:
path = plexpy.DATA_DIR
data = helpers.browse_path(path=path, filter_ext=filter_ext)
if data:
return {'result': 'success', 'path': path, 'data': data}
else:
return {'result': 'error', 'message': 'Invalid path.'}
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))