Started implement server side filechooser

pull/1768/head
Ozzieisaacs 4 years ago
parent 983e3b2274
commit 2508c1abb2

@ -5,7 +5,7 @@
# andy29485, idalin, Kyosfonica, wuqi, Kennyl, lemmsh,
# falgh1, grunjol, csitko, ytils, xybydy, trasba, vrabe,
# ruben-herold, marblepebble, JackED42, SiphonSquirrel,
# apetresc, nanu-c, mutschler
# apetresc, nanu-c, mutschler, GammaC0de, vuolter
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -26,6 +26,7 @@ import re
import base64
import json
import time
import operator
from datetime import datetime, timedelta
from babel import Locale as LC
@ -520,6 +521,89 @@ def list_restriction(res_type):
return response
@admi.route("/ajax/pathchooser/", endpoint="pathchooser")
@admi.route("/ajax/filechooser/", endpoint="filechooser")
@login_required
@admin_required
def pathchooser():
browse_for = "folder" if request.endpoint == "admin.pathchooser" else "file"
path = os.path.normpath(request.args.get('path', ""))
if os.path.isfile(path):
oldfile = path
path = os.path.dirname(path)
else:
oldfile = ""
abs = False
if os.path.isdir(path):
if os.path.isabs(path):
cwd = os.path.realpath(path)
abs = True
else:
cwd = os.path.relpath(path)
else:
cwd = os.getcwd()
cwd = os.path.normpath(os.path.realpath(cwd))
parentdir = os.path.dirname(cwd)
if not abs:
if os.path.realpath(cwd) == os.path.realpath("/"):
cwd = os.path.relpath(cwd)
else:
cwd = os.path.relpath(cwd) + os.path.sep
parentdir = os.path.relpath(parentdir) + os.path.sep
if os.path.realpath(cwd) == os.path.realpath("/"):
parentdir = ""
try:
folders = os.listdir(cwd)
except Exception:
folders = []
files = []
locale = get_locale()
for f in folders:
try:
data = {"name": f, "fullpath": os.path.join(cwd, f)}
data["sort"] = data["fullpath"].lower()
data["modified"] = format_datetime(datetime.fromtimestamp(int(os.path.getmtime(os.path.join(cwd, f)))),
format='short', locale=locale)
data["ext"] = os.path.splitext(f)[1]
except Exception:
continue
if os.path.isfile(os.path.join(cwd, f)):
data["type"] = "file"
data["size"] = os.path.getsize(os.path.join(cwd, f))
power = 0
while (data["size"] >> 10) > 0.3:
power += 1
data["size"] >>= 10
units = ("", "K", "M", "G", "T")
data["size"] = str(data["size"]) + " " + units[power] + "Byte"
else:
data["type"] = "dir"
data["size"] = ""
files.append(data)
files = sorted(files, key=operator.itemgetter("type", "sort"))
context = {
"cwd": cwd,
"files": files,
"parentdir": parentdir,
"type": browse_for,
"oldfile": oldfile,
"absolute": abs,
}
return json.dumps(context)
@admi.route("/config", methods=["GET", "POST"])
@unconfigured
def basic_configuration():

@ -37,7 +37,7 @@ from flask_login import login_required
from . import logger, gdriveutils, config, ub, calibre_db
from .web import admin_required
gdrive = Blueprint('gdrive', __name__)
gdrive = Blueprint('gdrive', __name__, url_prefix='/gdrive')
log = logger.create()
try:
@ -50,7 +50,7 @@ current_milli_time = lambda: int(round(time() * 1000))
gdrive_watch_callback_token = 'target=calibreweb-watch_files'
@gdrive.route("/gdrive/authenticate")
@gdrive.route("/authenticate")
@login_required
@admin_required
def authenticate_google_drive():
@ -63,7 +63,7 @@ def authenticate_google_drive():
return redirect(authUrl)
@gdrive.route("/gdrive/callback")
@gdrive.route("/callback")
def google_drive_callback():
auth_code = request.args.get('code')
if not auth_code:
@ -77,19 +77,14 @@ def google_drive_callback():
return redirect(url_for('admin.configuration'))
@gdrive.route("/gdrive/watch/subscribe")
@gdrive.route("/watch/subscribe")
@login_required
@admin_required
def watch_gdrive():
if not config.config_google_drive_watch_changes_response:
with open(gdriveutils.CLIENT_SECRETS, 'r') as settings:
filedata = json.load(settings)
# ToDo: Easier: rstrip('/')
if filedata['web']['redirect_uris'][0].endswith('/'):
filedata['web']['redirect_uris'][0] = filedata['web']['redirect_uris'][0][:-((len('/gdrive/callback')+1))]
else:
filedata['web']['redirect_uris'][0] = filedata['web']['redirect_uris'][0][:-(len('/gdrive/callback'))]
address = '%s/gdrive/watch/callback' % filedata['web']['redirect_uris'][0]
address = filedata['web']['redirect_uris'][0].rstrip('/').replace('/gdrive/callback', '/gdrive/watch/callback')
notification_id = str(uuid4())
try:
result = gdriveutils.watchChange(gdriveutils.Gdrive.Instance().drive, notification_id,
@ -99,14 +94,15 @@ def watch_gdrive():
except HttpError as e:
reason=json.loads(e.content)['error']['errors'][0]
if reason['reason'] == u'push.webhookUrlUnauthorized':
flash(_(u'Callback domain is not verified, please follow steps to verify domain in google developer console'), category="error")
flash(_(u'Callback domain is not verified, '
u'please follow steps to verify domain in google developer console'), category="error")
else:
flash(reason['message'], category="error")
return redirect(url_for('admin.configuration'))
@gdrive.route("/gdrive/watch/revoke")
@gdrive.route("/watch/revoke")
@login_required
@admin_required
def revoke_watch_gdrive():
@ -122,14 +118,14 @@ def revoke_watch_gdrive():
return redirect(url_for('admin.configuration'))
@gdrive.route("/gdrive/watch/callback", methods=['GET', 'POST'])
@gdrive.route("/watch/callback", methods=['GET', 'POST'])
def on_received_watch_confirmation():
if not config.config_google_drive_watch_changes_response:
return ''
if request.headers.get('X-Goog-Channel-Token') != gdrive_watch_callback_token \
or request.headers.get('X-Goog-Resource-State') != 'change' \
or not request.data:
return redirect(url_for('admin.configuration'))
return '' # redirect(url_for('admin.configuration'))
log.debug('%r', request.headers)
log.debug('%r', request.data)

@ -213,6 +213,45 @@ $(function() {
});
}
function fillFileTable(path, type) {
if (type === "dir") {
var request_path = "/../../ajax/pathchooser/";
} else {
var request_path = "/../../ajax/filechooser/";
}
$.ajax({
dataType: "json",
data: {
path: path,
},
url: window.location.pathname + request_path,
success: function success(data) {
$("#file_table > tbody > tr").each(function () {
if ($(this).attr("id") !== "parent") {
$(this).closest("tr").remove();
}
});
if (data.parentdir !== "") {
$("#parent").removeClass('hidden')
} else {
$("#parent").addClass('hidden')
}
// console.log(data);
data.files.forEach(function(entry) {
if(entry.type === "dir") {
var type = "<span class=\"glyphicon glyphicon-folder-close\"></span>";
} else {
var type = "";
}
$("<tr class=\"tr-clickable\" data-type=\"" + entry.type + "\" data-path=\"" +
entry.fullpath + "\"><td>" + type + "</td><td>" + entry.name + "</td><td>" +
entry.size + "</td></tr>").appendTo($("#file_table"));
});
},
timeout: 2000
});
}
$(".discover .row").isotope({
// options
itemSelector : ".book",
@ -402,6 +441,81 @@ $(function() {
$("#config_delete_kobo_token").show();
});
$("#fileModal").on("show.bs.modal", function(e) {
//get data-id attribute of the clicked element and store in button
//var submit = true;
//var cwd = "{{oldfile|default(cwd, True)|abspath|replace('\\', '\\\\')}}";
//var isabsolute = true;
fillFileTable("","dir");
});
//(".tr-clickable").on("click",
$(document).on("click", ".tr-clickable", function() {
var path = this.attributes['data-path'].value;
var type = this.attributes['data-type'].value;
fillFileTable(path, type);
});
/*{% if type == 'folder' %} {# browsing for folder #}
var abspath = "{{url_for('app.pathchooser') + '?path=' + cwd|abspath|quote_plus}}";
var relpath = "{{url_for('app.pathchooser') + '?path=' + cwd|relpath|quote_plus}}";
{% else %} {# browsing for file #}
var abspath = "{{url_for('app.filechooser') + '?path=' + oldfile|default(cwd, True)|abspath|quote_plus}}";
var relpath = "{{url_for('app.filechooser') + '?path=' + oldfile|default(cwd, True)|relpath|quote_plus}}";
{% endif %}*/
/*document.addEventListener("readystatechange", function(event) {
if (this.readyState === "complete") {
document.getElementById("tbody").style.height = (window.innerHeight - 25) + "px";
window.onresize = function (event) {
document.getElementById("tbody").style.height = (window.innerHeight - 25) + "px";
};
var clickables = document.getElementsByClassName("tr-clickable");
for (var i = 0; i < clickables.length; i++) {
clickables[i].onclick = (function () {
var onclick = clickables[i].onclick;
return function (e) {
if (onclick != null && !onclick()) {
return false
}
if (this.dataset.href !== undefined && this.dataset.href !== "#") {
window.location.href = this.dataset.href;
return false
} else {
return true;
}
}
})();
}
}
});
function updateParent()
{
if (window.top.SettingsUI !== undefined) {
window.top.SettingsUI.prototype.pathchooserChanged(this);
}
}
function setInvalid() {
submit = false;
cwd = "";
updateParent();
}
function setValid() {
submit = true;
updateParent();
}
function setFile(fullpath, name)
{
cwd = fullpath;
/*{*% if type == "file" %} {# browsing for file #}
abspath = "{{url_for('app.filechooser')}}?path={{cwd|abspath|quote_plus}}" + encodeURIComponent(name);
relpath = "{{url_for('app.filechooser')}}?path={{cwd|relpath|quote_plus}}" + encodeURIComponent(name);
{% endif %}*/
/*setValid();
}*/
$("#btndeletetoken").click(function() {
//get data-id attribute of the clicked element
var pathname = document.getElementsByTagName("script"), src = pathname[pathname.length - 1].src;

@ -384,7 +384,7 @@
<div class="form-group input-group">
<input type="text" class="form-control" id="config_converterpath" name="config_converterpath" value="{% if config.config_converterpath != None %}{{ config.config_converterpath }}{% endif %}" autocomplete="off">
<span class="input-group-btn">
<button type="button" id="converter_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
<button type="button" id="converter_modal_path" data-toggle="modal" data-target="#fileModal" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
</span>
</div>
<div class="form-group">
@ -412,8 +412,6 @@
</div>
{% endif %}
</div>
<div class="col-sm-12">
{% if not show_login_button %}
<button type="submit" name="submit" class="btn btn-default">{{_('Save')}}</button>
@ -428,6 +426,9 @@
</form>
</div>
{% endblock %}
{% block modal %}
{{ filechooser_modal() }}
{% endblock %}
{% block js %}
<script type="text/javascript">
$(document).on('change', '#config_use_google_drive', function() {

@ -1,4 +1,4 @@
{% from 'modal_dialogs.html' import restrict_modal, delete_book %}
{% from 'modal_dialogs.html' import restrict_modal, delete_book, filechooser_modal %}
<!DOCTYPE html>
<html lang="{{ g.user.locale }}">
<head>

@ -68,3 +68,36 @@
</div>
{% endif %}
{% endmacro %}
{% macro filechooser_modal() %}
<div class="modal fade" id="fileModal" role="dialog" aria-labelledby="metafileLabel">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-info text-center">
<span>{{_('Choose File Location')}}</span>
</div>
<div class="modal-body">
<table id="file_table" class="table table-striped">
<thead>
<tr>
<th>{{_('type')}}</th>
<th>{{_('name')}}</th>
<th>{{_('size')}}</th>
</tr>
</thead>
<tbody id="tbody">
<tr class="tr-clickable hidden" id="parent" data-type="dir" data-path="..">
<td><span class="glyphicon glyphicon-folder-close"></span></td>
<td title="{{_('Parent Directory')}}"><span class="parentdir">..</span></td>
<td></td>
</tr>
</tbody>
</table>
</div>
<div class="modal-footer">
<input type="button" class="btn btn-primary" value="{{_('Select')}}" name="file_confirm" id="file_confirm" data-dismiss="modal">
<button type="button" class="btn btn-default" data-dismiss="modal">{{_('Cancel')}}</button>
</div>
</div>
</div>
</div>
{% endmacro %}

Loading…
Cancel
Save