1
0
mirror of https://github.com/janeczku/calibre-web.git synced 2024-11-28 08:58:41 +02:00

Additional glyphicons for music on search and author page

Fix duplicate user and email (now case insensitive)
Output of calibre on stderr is now logged (full traceback in debug-log, otherwise, only errormessage)
Natural sorting for comic reader
Fix for long running tasks
This commit is contained in:
Ozzieisaacs 2019-06-30 09:02:59 +02:00
parent ad44e58c7a
commit 499a66dfb0
12 changed files with 153 additions and 113 deletions

View File

@ -603,13 +603,23 @@ def new_user():
return render_title_template("user_edit.html", new_user=1, content=content, translations=translations,
registered_oauth=oauth_check, title=_(u"Add new user"))
content.password = generate_password_hash(to_save["password"])
content.nickname = to_save["nickname"]
if config.config_public_reg and not check_valid_domain(to_save["email"]):
flash(_(u"E-mail is not from valid domain"), category="error")
return render_title_template("user_edit.html", new_user=1, content=content, translations=translations,
registered_oauth=oauth_check, title=_(u"Add new user"))
existing_user = ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == to_save["nickname"].lower())\
.first()
existing_email = ub.session.query(ub.User).filter(ub.User.email == to_save["email"].lower())\
.first()
if not existing_user and not existing_email:
content.nickname = to_save["nickname"]
if config.config_public_reg and not check_valid_domain(to_save["email"]):
flash(_(u"E-mail is not from valid domain"), category="error")
return render_title_template("user_edit.html", new_user=1, content=content, translations=translations,
registered_oauth=oauth_check, title=_(u"Add new user"))
else:
content.email = to_save["email"]
else:
content.email = to_save["email"]
flash(_(u"Found an existing account for this e-mail address or nickname."), category="error")
return render_title_template("user_edit.html", new_user=1, content=content, translations=translations,
languages=languages, title=_(u"Add new user"), page="newuser",
registered_oauth=oauth_check)
try:
ub.session.add(content)
ub.session.commit()
@ -753,7 +763,16 @@ def edit_user(user_id):
if "locale" in to_save and to_save["locale"]:
content.locale = to_save["locale"]
if to_save["email"] and to_save["email"] != content.email:
content.email = to_save["email"]
existing_email = ub.session.query(ub.User).filter(ub.User.email == to_save["email"].lower()) \
.first()
if not existing_email:
content.email = to_save["email"]
else:
flash(_(u"Found an existing account for this e-mail address."), category="error")
return render_title_template("user_edit.html", translations=translations, languages=languages,
new_user=0, content=content, downloads=downloads, registered_oauth=oauth_check,
title=_(u"Edit User %(nick)s", nick=content.nickname), page="edituser")
if "kindle_mail" in to_save and to_save["kindle_mail"] != content.kindle_mail:
content.kindle_mail = to_save["kindle_mail"]
try:

View File

@ -30,13 +30,14 @@ import requests
import shutil
import time
import unicodedata
from datetime import datetime
from datetime import datetime, timedelta
from functools import reduce
from tempfile import gettempdir
from babel import Locale as LC
from babel.core import UnknownLocaleError
from babel.dates import format_datetime
from babel.dates import format_datetime, format_timedelta
from babel.units import format_unit
from flask import send_from_directory, make_response, redirect, abort
from flask_babel import gettext as _
from flask_login import current_user
@ -589,8 +590,34 @@ def json_serial(obj):
if isinstance(obj, (datetime)):
return obj.isoformat()
if isinstance(obj, (timedelta)):
return {
'__type__': 'timedelta',
'days': obj.days,
'seconds': obj.seconds,
'microseconds': obj.microseconds,
}
# return obj.isoformat()
raise TypeError ("Type %s not serializable" % type(obj))
# helper function for displaying the runtime of tasks
def format_runtime(runtime):
retVal = ""
if runtime.days:
retVal = format_unit(runtime.days, 'duration-day', length="long", locale=web.get_locale()) + ', '
mins, seconds = divmod(runtime.seconds, 60)
hours, minutes = divmod(mins, 60)
# ToDo: locale.number_symbols._data['timeSeparator'] -> localize time separator ?
if hours:
retVal += '{:d}:{:02d}:{:02d}s'.format(hours, minutes, seconds)
elif minutes:
retVal += '{:2d}:{:02d}s'.format(minutes, seconds)
else:
retVal += '{:2d}s'.format(seconds)
return retVal
# helper function to apply localize status information in tasklist entries
def render_task_status(tasklist):
renderedtasklist=list()
@ -603,6 +630,8 @@ def render_task_status(tasklist):
if 'starttime' not in task:
task['starttime'] = ""
task['runtime'] = format_runtime(task['formRuntime'])
# localize the task status
if isinstance( task['stat'], int ):
if task['stat'] == STAT_WAITING:

View File

@ -107,7 +107,7 @@ def setup(log_file, log_level=None):
return
r.debug("logging to %s level %s", log_file, r.level)
if 1 == 1: # log_file == LOG_TO_STDERR:
if log_file == LOG_TO_STDERR:
file_handler = StreamHandler()
file_handler.baseFilename = LOG_TO_STDERR
else:

View File

@ -46,14 +46,13 @@ ldap_support = ldap1.ldap_supported()
def requires_basic_auth_if_no_ano(f):
@wraps(f)
def decorated(*args, **kwargs):
if config.config_login_type == 1 and ldap_support:
return ldap1.ldap.basic_auth_required(*args, **kwargs)
auth = request.authorization
if config.config_anonbrowse != 1:
if not auth or not check_auth(auth.username, auth.password):
return authenticate()
return f(*args, **kwargs)
if config.config_login_type == 1 and ldap_support:
return ldap1.ldap.basic_auth_required(f)
return decorated

View File

@ -1,3 +1,67 @@
/* alphanum.js (C) Brian Huisman
* Based on the Alphanum Algorithm by David Koelle
* The Alphanum Algorithm is discussed at http://www.DaveKoelle.com
*
* Distributed under same license as original
*
* Released under the MIT License - https://opensource.org/licenses/MIT
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
* USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* ********************************************************************
* Alphanum sort() function version - case insensitive
* - Slower, but easier to modify for arrays of objects which contain
* string properties
*
*/
function alphanumCase(a, b) {
function chunkify(t) {
var tz = new Array();
var x = 0, y = -1, n = 0, i, j;
while (i = (j = t.charAt(x++)).charCodeAt(0)) {
var m = (i == 46 || (i >=48 && i <= 57));
if (m !== n) {
tz[++y] = "";
n = m;
}
tz[y] += j;
}
return tz;
}
var aa = chunkify(a.filename.toLowerCase());
var bb = chunkify(b.filename.toLowerCase());
for (x = 0; aa[x] && bb[x]; x++) {
if (aa[x] !== bb[x]) {
var c = Number(aa[x]), d = Number(bb[x]);
if (c == aa[x] && d == bb[x]) {
return c - d;
} else return (aa[x] > bb[x]) ? 1 : -1;
}
}
return aa.length - bb.length;
}
// ===========================================================================
/**
* archive.js
*
@ -13,22 +77,6 @@
var bitjs = bitjs || {};
bitjs.archive = bitjs.archive || {};
function naturalCompare(a, b) {
var ax = [], bx = [];
a.filename.toLowerCase().replace(/(\d+)|(\D+)/g, function(_, $1, $2) { ax.push([$1 || Infinity, $2 || ""]) });
b.filename.toLowerCase().replace(/(\d+)|(\D+)/g, function(_, $1, $2) { bx.push([$1 || Infinity, $2 || ""]) });
while(ax.length && bx.length) {
var an = ax.shift();
var bn = bx.shift();
var nn = (an[0] - bn[0]) || an[1].localeCompare(bn[1]);
if(nn) return nn;
}
return ax.length - bx.length;
}
(function() {
// ===========================================================================

View File

@ -1332,13 +1332,7 @@ var unrar = function(arrayBuffer) {
totalFilesInArchive = localFiles.length;
// now we have all information but things are unpacked
// TODO: unpack
localFiles.sort(naturalCompare);
/*localFiles = localFiles.sort(function(a, b) {
var aname = a.filename.toLowerCase();
var bname = b.filename.toLowerCase();
return aname > bname ? 1 : -1;
});*/
localFiles.sort(alphanumCase);
info(localFiles.map(function(a) {
return a.filename;

View File

@ -136,7 +136,8 @@ var untar = function(arrayBuffer) {
allLocalFiles.push(localFile);
postProgress();
}
allLocalFiles.sort(naturalCompare);
// got all local files, now sort them
allLocalFiles.sort(alphanumCase);
allLocalFiles.forEach(function(oneLocalFile) {
// While we don't encounter an empty block, keep making TarLocalFiles.

View File

@ -162,12 +162,7 @@ var unzip = function(arrayBuffer) {
totalFilesInArchive = localFiles.length;
// got all local files, now sort them
localFiles.sort(naturalCompare);
/*localFiles.sort(function(a, b) {
var aname = a.filename.toLowerCase();
var bname = b.filename.toLowerCase();
return aname > bname ? 1 : -1;
});*/
localFiles.sort(alphanumCase);
// archive extra data record
if (bstream.peekNumber(4) === zArchiveExtraDataSignature) {
@ -663,51 +658,3 @@ function inflate(compressedData, numDecompressedBytes) {
onmessage = function(event) {
unzip(event.data.file, true);
};
/*
function naturalCompare(a, b) {
var ax = [], bx = [];
a.filename.toLowerCase().replace(/(\d+)|(\D+)/g, function(_, $1, $2) { ax.push([$1 || Infinity, $2 || ""]) });
b.filename.toLowerCase().replace(/(\d+)|(\D+)/g, function(_, $1, $2) { bx.push([$1 || Infinity, $2 || ""]) });
while(ax.length && bx.length) {
var an = ax.shift();
var bn = bx.shift();
var nn = (an[0] - bn[0]) || an[1].localeCompare(bn[1]);
if(nn) return nn;
}
return ax.length - bx.length;
}*/
/*var re = /([a-z]+)(\d+)(.+)/i;
function naturalCompare(a, b) {
var ma = a.match(re),
mb = b.match(re),
a_str = ma[1],
b_str = mb[1],
a_num = parseInt(ma[2],10),
b_num = parseInt(mb[2],10),
a_rem = ma[3],
b_rem = mb[3];
return a_str > b_str ? 1 : a_str < b_str ? -1 : a_num > b_num ? 1 : a_num < b_num ? -1 : a_rem > b_rem;
}*/
/*function naturalCompare(a, b) {
var ax = [], bx = [];
a.replace(/(\d+)|(\D+)/g, function(_, $1, $2) { ax.push([$1 || Infinity, $2 || ""]) });
b.replace(/(\d+)|(\D+)/g, function(_, $1, $2) { bx.push([$1 || Infinity, $2 || ""]) });
while(ax.length && bx.length) {
var an = ax.shift();
var bn = bx.shift();
var nn = (an[0] - bn[0]) || an[1].localeCompare(bn[1]);
if(nn) return nn;
}
return ax.length - bx.length;
}*/

View File

@ -23,7 +23,7 @@ import os
import subprocess
def process_open(command, quotes=(), env=None, sout=subprocess.PIPE):
def process_open(command, quotes=(), env=None, sout=subprocess.PIPE, serr=subprocess.PIPE):
# Linux py2.7 encode as list without quotes no empty element for parameters
# linux py3.x no encode and as list without quotes no empty element for parameters
# windows py2.7 encode as string with quotes empty element for parameters is okay
@ -42,4 +42,4 @@ def process_open(command, quotes=(), env=None, sout=subprocess.PIPE):
else:
exc_command = [x for x in command]
return subprocess.Popen(exc_command, shell=False, stdout=sout, universal_newlines=True, env=env)
return subprocess.Popen(exc_command, shell=False, stdout=sout, stderr=serr, universal_newlines=True, env=env)

View File

@ -64,6 +64,11 @@
<a class="author-name" href="{{url_for('web.books_list', data='author', sort='new', book_id=author.id) }}">{{author.name.replace('|',',')|shortentitle(30)}}</a>
{% endif %}
{% endfor %}
{% for format in entry.data %}
{% if format.format|lower == 'mp3' %}
<span class="glyphicon glyphicon-music"></span>
{% endif %}
{% endfor %}
</p>
{% if entry.ratings.__len__() > 0 %}
<div class="rating">

View File

@ -61,7 +61,7 @@
{% if loop.index > g.config_authors_max and g.config_authors_max != 0 %}
{% if not loop.first %}
<span class="author-hidden-divider">&amp;</span>
{% endif %}
{% endif %}
<a class="author-name author-hidden" href="{{url_for('web.books_list', data='author', sort='new', book_id=author.id) }}">{{author.name.replace('|',',')|shortentitle(30)}}</a>
{% if loop.last %}
<a href="#" class="author-expand" data-authors-max="{{g.config_authors_max}}" data-collapse-caption="({{_('reduce')}})">(...)</a>
@ -73,6 +73,11 @@
<a class="author-name" href="{{url_for('web.books_list', data='author', sort='new', book_id=author.id) }}">{{author.name.replace('|',',')|shortentitle(30)}}</a>
{% endif %}
{% endfor %}
{% for format in entry.data %}
{% if format.format|lower == 'mp3' %}
<span class="glyphicon glyphicon-music"></span>
{% endif %}
{% endfor %}
</p>
{% if entry.ratings.__len__() > 0 %}
<div class="rating">

View File

@ -25,7 +25,7 @@ import smtplib
import socket
import time
import threading
from datetime import datetime
from datetime import datetime, timedelta
try:
from StringIO import StringIO
@ -226,8 +226,10 @@ class WorkerThread(threading.Thread):
if self.UIqueue[self.current]['stat'] == STAT_STARTED:
if self.queue[self.current]['taskType'] == TASK_EMAIL:
self.UIqueue[self.current]['progress'] = self.get_send_status()
self.UIqueue[self.current]['runtime'] = self._formatRuntime(
datetime.now() - self.queue[self.current]['starttime'])
self.UIqueue[self.current]['formRuntime'] = datetime.now() - self.queue[self.current]['starttime']
self.UIqueue[self.current]['rt'] = self.UIqueue[self.current]['formRuntime'].days*24*60 \
+ self.UIqueue[self.current]['formRuntime'].seconds \
+ self.UIqueue[self.current]['formRuntime'].microseconds
return self.UIqueue
def _convert_any_format(self):
@ -336,6 +338,11 @@ class WorkerThread(threading.Thread):
# process returncode
check = p.returncode
calibre_traceback = p.stderr.readlines()
for ele in calibre_traceback:
log.debug(ele.strip('\n'))
if not ele.startswith('Traceback') and not ele.startswith(' File'):
error_message = "Calibre failed with error: %s" % ele.strip('\n')
# kindlegen returncodes
# 0 = Info(prcgen):I1036: Mobi file built successfully
@ -491,28 +498,14 @@ class WorkerThread(threading.Thread):
self._handleError(u'Error sending email: ' + e.strerror)
return None
def _formatRuntime(self, runtime):
self.UIqueue[self.current]['rt'] = runtime.total_seconds()
val = re.split('\:|\.', str(runtime))[0:3]
erg = list()
for v in val:
if int(v) > 0:
erg.append(v)
retVal = (':'.join(erg)).lstrip('0') + ' s'
if retVal == ' s':
retVal = '0 s'
return retVal
def _handleError(self, error_message):
log.error(error_message)
self.UIqueue[self.current]['stat'] = STAT_FAIL
self.UIqueue[self.current]['progress'] = "100 %"
self.UIqueue[self.current]['runtime'] = self._formatRuntime(
datetime.now() - self.queue[self.current]['starttime'])
self.UIqueue[self.current]['formRuntime'] = datetime.now() - self.queue[self.current]['starttime']
self.UIqueue[self.current]['message'] = error_message
def _handleSuccess(self):
self.UIqueue[self.current]['stat'] = STAT_FINISH_SUCCESS
self.UIqueue[self.current]['progress'] = "100 %"
self.UIqueue[self.current]['runtime'] = self._formatRuntime(
datetime.now() - self.queue[self.current]['starttime'])
self.UIqueue[self.current]['formRuntime'] = datetime.now() - self.queue[self.current]['starttime']