Merge branch 'master' into Develop

This commit is contained in:
Ozzieisaacs 2021-05-01 20:54:01 +02:00
commit b34672ed19
72 changed files with 14114 additions and 8455 deletions

View File

@ -31,13 +31,13 @@ from datetime import datetime, timedelta
from babel import Locale as LC from babel import Locale as LC
from babel.dates import format_datetime from babel.dates import format_datetime
from flask import Blueprint, flash, redirect, url_for, abort, request, make_response, send_from_directory, g from flask import Blueprint, flash, redirect, url_for, abort, request, make_response, send_from_directory, g, Response
from flask_login import login_required, current_user, logout_user, confirm_login from flask_login import login_required, current_user, logout_user, confirm_login
from flask_babel import gettext as _ from flask_babel import gettext as _
from sqlalchemy import and_ from sqlalchemy import and_
from sqlalchemy.orm.attributes import flag_modified from sqlalchemy.orm.attributes import flag_modified
from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError
from sqlalchemy.sql.expression import func, or_ from sqlalchemy.sql.expression import func, or_, text
from . import constants, logger, helper, services from . import constants, logger, helper, services
from .cli import filepicker from .cli import filepicker
@ -46,7 +46,7 @@ from .helper import check_valid_domain, send_test_mail, reset_password, generate
valid_email, check_username valid_email, check_username
from .gdriveutils import is_gdrive_ready, gdrive_support from .gdriveutils import is_gdrive_ready, gdrive_support
from .render_template import render_title_template, get_sidebar_config from .render_template import render_title_template, get_sidebar_config
from . import debug_info from . import debug_info, _BABEL_TRANSLATIONS
try: try:
from functools import wraps from functools import wraps
@ -224,11 +224,23 @@ def edit_user_table():
languages = calibre_db.speaking_language() languages = calibre_db.speaking_language()
translations = babel.list_translations() + [LC('en')] translations = babel.list_translations() + [LC('en')]
allUser = ub.session.query(ub.User) allUser = ub.session.query(ub.User)
tags = calibre_db.session.query(db.Tags)\
.join(db.books_tags_link)\
.join(db.Books)\
.filter(calibre_db.common_filters()) \
.group_by(text('books_tags_link.tag'))\
.order_by(db.Tags.name).all()
if config.config_restricted_column:
custom_values = calibre_db.session.query(db.cc_classes[config.config_restricted_column]).all()
else:
custom_values = []
if not config.config_anonbrowse: if not config.config_anonbrowse:
allUser = allUser.filter(ub.User.role.op('&')(constants.ROLE_ANONYMOUS) != constants.ROLE_ANONYMOUS) allUser = allUser.filter(ub.User.role.op('&')(constants.ROLE_ANONYMOUS) != constants.ROLE_ANONYMOUS)
return render_title_template("user_table.html", return render_title_template("user_table.html",
users=allUser.all(), users=allUser.all(),
tags=tags,
custom_values=custom_values,
translations=translations, translations=translations,
languages=languages, languages=languages,
visiblility=visibility, visiblility=visibility,
@ -237,26 +249,41 @@ def edit_user_table():
title=_(u"Edit Users"), title=_(u"Edit Users"),
page="usertable") page="usertable")
@admi.route("/ajax/listusers") @admi.route("/ajax/listusers")
@login_required @login_required
@admin_required @admin_required
def list_users(): def list_users():
off = request.args.get("offset") or 0 off = int(request.args.get("offset") or 0)
limit = request.args.get("limit") or 10 limit = int(request.args.get("limit") or 10)
search = request.args.get("search") search = request.args.get("search")
sort = request.args.get("sort", "id")
order = request.args.get("order", "").lower()
state = None
if sort == "state":
state = json.loads(request.args.get("state", "[]"))
if sort != "state" and order:
order = text(sort + " " + order)
elif not state:
order = ub.User.id.asc()
all_user = ub.session.query(ub.User) all_user = ub.session.query(ub.User)
if not config.config_anonbrowse: if not config.config_anonbrowse:
all_user = all_user.filter(ub.User.role.op('&')(constants.ROLE_ANONYMOUS) != constants.ROLE_ANONYMOUS) all_user = all_user.filter(ub.User.role.op('&')(constants.ROLE_ANONYMOUS) != constants.ROLE_ANONYMOUS)
total_count = all_user.count()
total_count = filtered_count = all_user.count()
if search: if search:
users = all_user.filter(or_(func.lower(ub.User.name).ilike("%" + search + "%"), all_user = all_user.filter(or_(func.lower(ub.User.name).ilike("%" + search + "%"),
func.lower(ub.User.kindle_mail).ilike("%" + search + "%"), func.lower(ub.User.kindle_mail).ilike("%" + search + "%"),
func.lower(ub.User.email).ilike("%" + search + "%")))\ func.lower(ub.User.email).ilike("%" + search + "%")))
.offset(off).limit(limit).all() if state:
filtered_count = len(users) users = calibre_db.get_checkbox_sorted(all_user.all(), state, off, limit, request.args.get("order", "").lower())
else: else:
users = all_user.offset(off).limit(limit).all() users = all_user.order_by(order).offset(off).limit(limit).all()
filtered_count = total_count if search:
filtered_count = len(users)
for user in users: for user in users:
if user.default_language == "all": if user.default_language == "all":
@ -270,12 +297,38 @@ def list_users():
response.headers["Content-Type"] = "application/json; charset=utf-8" response.headers["Content-Type"] = "application/json; charset=utf-8"
return response return response
@admi.route("/ajax/deleteuser") @admi.route("/ajax/deleteuser", methods=['POST'])
@login_required @login_required
@admin_required @admin_required
def delete_user(): def delete_user():
# ToDo User delete check also not last one user_ids = request.form.to_dict(flat=False)
return "" users = None
if "userid[]" in user_ids:
users = ub.session.query(ub.User).filter(ub.User.id.in_(user_ids['userid[]'])).all()
elif "userid" in user_ids:
users = ub.session.query(ub.User).filter(ub.User.id == user_ids['userid'][0]).all()
count = 0
errors = list()
success = list()
if not users:
log.error("User not found")
return Response(json.dumps({'type': "danger", 'message': _("User not found")}), mimetype='application/json')
for user in users:
try:
message = _delete_user(user)
count += 1
except Exception as ex:
log.error(ex)
errors.append({'type': "danger", 'message': str(ex)})
if count == 1:
log.info("User {} deleted".format(user_ids))
success = [{'type': "success", 'message': message}]
elif count > 1:
log.info("Users {} deleted".format(user_ids))
success = [{'type': "success", 'message': _("{} users deleted successfully").format(count)}]
success.extend(errors)
return Response(json.dumps(success), mimetype='application/json')
@admi.route("/ajax/getlocale") @admi.route("/ajax/getlocale")
@login_required @login_required
@ -295,7 +348,7 @@ def table_get_locale():
def table_get_default_lang(): def table_get_default_lang():
languages = calibre_db.speaking_language() languages = calibre_db.speaking_language()
ret = list() ret = list()
ret.append({'value':'all','text':_('Show All')}) ret.append({'value': 'all', 'text': _('Show All')})
for lang in languages: for lang in languages:
ret.append({'value': lang.lang_code, 'text': lang.name}) ret.append({'value': lang.lang_code, 'text': lang.name})
return json.dumps(ret) return json.dumps(ret)
@ -316,52 +369,89 @@ def edit_list_user(param):
if "pk[]" in vals: if "pk[]" in vals:
users = all_user.filter(ub.User.id.in_(vals['pk[]'])).all() users = all_user.filter(ub.User.id.in_(vals['pk[]'])).all()
else: else:
return "" return _("Malformed request"), 400
if 'field_index' in vals: if 'field_index' in vals:
vals['field_index'] = vals['field_index'][0] vals['field_index'] = vals['field_index'][0]
if 'value' in vals: if 'value' in vals:
vals['value'] = vals['value'][0] vals['value'] = vals['value'][0]
else: elif not ('value[]' in vals):
return "" return _("Malformed request"), 400
for user in users: for user in users:
try: try:
vals['value'] = vals['value'].strip() if param in ['denied_tags', 'allowed_tags', 'allowed_column_value', 'denied_column_value']:
if param == 'name': if 'value[]' in vals:
if user.name == "Guest": setattr(user, param, prepare_tags(user, vals['action'][0], param, vals['value[]']))
raise Exception(_("Guest Name can't be changed"))
user.name = check_username(vals['value'])
elif param =='email':
user.email = check_email(vals['value'])
elif param == 'kindle_mail':
user.kindle_mail = valid_email(vals['value']) if vals['value'] else ""
elif param == 'role':
if vals['value'] == 'true':
user.role |= int(vals['field_index'])
else: else:
if int(vals['field_index']) == constants.ROLE_ADMIN: setattr(user, param, vals['value'].strip())
if not ub.session.query(ub.User).\ else:
filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN, vals['value'] = vals['value'].strip()
ub.User.id != user.id).count(): if param == 'name':
return _(u"No admin user remaining, can't remove admin role", nick=user.name), 400 if user.name == "Guest":
user.role &= ~int(vals['field_index']) raise Exception(_("Guest Name can't be changed"))
elif param == 'sidebar_view': user.name = check_username(vals['value'])
if vals['value'] == 'true': elif param =='email':
user.sidebar_view |= int(vals['field_index']) user.email = check_email(vals['value'])
elif param == 'kindle_mail':
user.kindle_mail = valid_email(vals['value']) if vals['value'] else ""
elif param.endswith('role'):
value = int(vals['field_index'])
if user.name == "Guest" and value in \
[constants.ROLE_ADMIN, constants.ROLE_PASSWD, constants.ROLE_EDIT_SHELFS]:
raise Exception(_("Guest can't have this role"))
# check for valid value, last on checks for power of 2 value
if value > 0 and value <= constants.ROLE_VIEWER and (value & value-1 == 0 or value == 1):
if vals['value'] == 'true':
user.role |= value
elif vals['value'] == 'false':
if value == constants.ROLE_ADMIN:
if not ub.session.query(ub.User).\
filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN,
ub.User.id != user.id).count():
return Response(
json.dumps([{'type': "danger",
'message':_(u"No admin user remaining, can't remove admin role",
nick=user.name)}]), mimetype='application/json')
user.role &= ~value
else:
raise Exception(_("Value has to be true or false"))
else:
raise Exception(_("Invalid role"))
elif param.startswith('sidebar'):
value = int(vals['field_index'])
if user.name == "Guest" and value == constants.SIDEBAR_READ_AND_UNREAD:
raise Exception(_("Guest can't have this view"))
# check for valid value, last on checks for power of 2 value
if value > 0 and value <= constants.SIDEBAR_LIST and (value & value-1 == 0 or value == 1):
if vals['value'] == 'true':
user.sidebar_view |= value
elif vals['value'] == 'false':
user.sidebar_view &= ~value
else:
raise Exception(_("Value has to be true or false"))
else:
raise Exception(_("Invalid view"))
elif param == 'locale':
if user.name == "Guest":
raise Exception(_("Guest's Locale is determined automatically and can't be set"))
if vals['value'] in _BABEL_TRANSLATIONS:
user.locale = vals['value']
else:
raise Exception(_("No Valid Locale Given"))
elif param == 'default_language':
languages = calibre_db.session.query(db.Languages) \
.join(db.books_languages_link) \
.join(db.Books) \
.filter(calibre_db.common_filters()) \
.group_by(text('books_languages_link.lang_code')).all()
lang_codes = [lang.lang_code for lang in languages] + ["all"]
if vals['value'] in lang_codes:
user.default_language = vals['value']
else:
raise Exception(_("No Valid Book Language Given"))
else: else:
user.sidebar_view &= ~int(vals['field_index']) return _("Parameter not found"), 400
elif param == 'denied_tags':
user.denied_tags = vals['value']
elif param == 'allowed_tags':
user.allowed_tags = vals['value']
elif param == 'allowed_column_value':
user.allowed_column_value = vals['value']
elif param == 'denied_column_value':
user.denied_column_value = vals['value']
elif param == 'locale':
user.locale = vals['value']
elif param == 'default_language':
user.default_language = vals['value']
except Exception as ex: except Exception as ex:
log.debug_or_exception(ex)
return str(ex), 400 return str(ex), 400
ub.session_commit() ub.session_commit()
return "" return ""
@ -383,6 +473,21 @@ def update_table_settings():
return "Invalid request", 400 return "Invalid request", 400
return "" return ""
def check_valid_read_column(column):
if column != "0":
if not calibre_db.session.query(db.Custom_Columns).filter(db.Custom_Columns.id == column) \
.filter(and_(db.Custom_Columns.datatype == 'bool', db.Custom_Columns.mark_for_delete == 0)).all():
return False
return True
def check_valid_restricted_column(column):
if column != "0":
if not calibre_db.session.query(db.Custom_Columns).filter(db.Custom_Columns.id == column) \
.filter(and_(db.Custom_Columns.datatype == 'text', db.Custom_Columns.mark_for_delete == 0)).all():
return False
return True
@admi.route("/admin/viewconfig", methods=["POST"]) @admi.route("/admin/viewconfig", methods=["POST"])
@login_required @login_required
@ -398,12 +503,23 @@ def update_view_configuration():
if _config_string("config_title_regex"): if _config_string("config_title_regex"):
calibre_db.update_title_sort(config) calibre_db.update_title_sort(config)
if not check_valid_read_column(to_save.get("config_read_column", "0")):
flash(_(u"Invalid Read Column"), category="error")
log.debug("Invalid Read column")
return view_configuration()
_config_int("config_read_column") _config_int("config_read_column")
if not check_valid_restricted_column(to_save.get("config_restricted_column", "0")):
flash(_(u"Invalid Restricted Column"), category="error")
log.debug("Invalid Restricted Column")
return view_configuration()
_config_int("config_restricted_column")
_config_int("config_theme") _config_int("config_theme")
_config_int("config_random_books") _config_int("config_random_books")
_config_int("config_books_per_page") _config_int("config_books_per_page")
_config_int("config_authors_max") _config_int("config_authors_max")
_config_int("config_restricted_column")
config.config_default_role = constants.selected_roles(to_save) config.config_default_role = constants.selected_roles(to_save)
config.config_default_role &= ~constants.ROLE_ANONYMOUS config.config_default_role &= ~constants.ROLE_ANONYMOUS
@ -414,6 +530,7 @@ def update_view_configuration():
config.save() config.save()
flash(_(u"Calibre-Web configuration updated"), category="success") flash(_(u"Calibre-Web configuration updated"), category="success")
log.debug("Calibre-Web configuration updated")
before_request() before_request()
return view_configuration() return view_configuration()
@ -437,6 +554,8 @@ def load_dialogtexts(element_id):
texts["main"] = _('Are you sure you want to change visible book languages for selected user(s)?') texts["main"] = _('Are you sure you want to change visible book languages for selected user(s)?')
elif element_id == "role": elif element_id == "role":
texts["main"] = _('Are you sure you want to change the selected role for the selected user(s)?') texts["main"] = _('Are you sure you want to change the selected role for the selected user(s)?')
elif element_id == "restrictions":
texts["main"] = _('Are you sure you want to change the selected restrictions for the selected user(s)?')
elif element_id == "sidebar_view": elif element_id == "sidebar_view":
texts["main"] = _('Are you sure you want to change the selected visibility restrictions for the selected user(s)?') texts["main"] = _('Are you sure you want to change the selected visibility restrictions for the selected user(s)?')
return json.dumps(texts) return json.dumps(texts)
@ -583,6 +702,26 @@ def restriction_deletion(element, list_func):
return ','.join(elementlist) return ','.join(elementlist)
def prepare_tags(user, action, tags_name, id_list):
if "tags" in tags_name:
tags = calibre_db.session.query(db.Tags).filter(db.Tags.id.in_(id_list)).all()
if not tags:
raise Exception(_("Tag not found"))
new_tags_list = [x.name for x in tags]
else:
tags = calibre_db.session.query(db.cc_classes[config.config_restricted_column])\
.filter(db.cc_classes[config.config_restricted_column].id.in_(id_list)).all()
new_tags_list = [x.value for x in tags]
saved_tags_list = user.__dict__[tags_name].split(",") if len(user.__dict__[tags_name]) else []
if action == "remove":
saved_tags_list = [x for x in saved_tags_list if x not in new_tags_list]
elif action == "add":
saved_tags_list.extend(x for x in new_tags_list if x not in saved_tags_list)
else:
raise Exception(_("Invalid Action"))
return ",".join(saved_tags_list)
@admi.route("/ajax/addrestriction/<int:res_type>", defaults={"user_id": 0}, methods=['POST']) @admi.route("/ajax/addrestriction/<int:res_type>", defaults={"user_id": 0}, methods=['POST'])
@admi.route("/ajax/addrestriction/<int:res_type>/<int:user_id>", methods=['POST']) @admi.route("/ajax/addrestriction/<int:res_type>/<int:user_id>", methods=['POST'])
@login_required @login_required
@ -610,10 +749,10 @@ def add_restriction(res_type, user_id):
usr = current_user usr = current_user
if 'submit_allow' in element: if 'submit_allow' in element:
usr.allowed_tags = restriction_addition(element, usr.list_allowed_tags) usr.allowed_tags = restriction_addition(element, usr.list_allowed_tags)
ub.session_commit("Changed allowed tags of user {} to {}".format(usr.name, usr.list_allowed_tags)) ub.session_commit("Changed allowed tags of user {} to {}".format(usr.name, usr.list_allowed_tags()))
elif 'submit_deny' in element: elif 'submit_deny' in element:
usr.denied_tags = restriction_addition(element, usr.list_denied_tags) usr.denied_tags = restriction_addition(element, usr.list_denied_tags)
ub.session_commit("Changed denied tags of user {} to {}".format(usr.name, usr.list_denied_tags)) ub.session_commit("Changed denied tags of user {} to {}".format(usr.name, usr.list_denied_tags()))
if res_type == 3: # CustomC per user if res_type == 3: # CustomC per user
if isinstance(user_id, int): if isinstance(user_id, int):
usr = ub.session.query(ub.User).filter(ub.User.id == int(user_id)).first() usr = ub.session.query(ub.User).filter(ub.User.id == int(user_id)).first()
@ -622,11 +761,11 @@ def add_restriction(res_type, user_id):
if 'submit_allow' in element: if 'submit_allow' in element:
usr.allowed_column_value = restriction_addition(element, usr.list_allowed_column_values) usr.allowed_column_value = restriction_addition(element, usr.list_allowed_column_values)
ub.session_commit("Changed allowed columns of user {} to {}".format(usr.name, ub.session_commit("Changed allowed columns of user {} to {}".format(usr.name,
usr.list_allowed_column_values)) usr.list_allowed_column_values()))
elif 'submit_deny' in element: elif 'submit_deny' in element:
usr.denied_column_value = restriction_addition(element, usr.list_denied_column_values) usr.denied_column_value = restriction_addition(element, usr.list_denied_column_values)
ub.session_commit("Changed denied columns of user {} to {}".format(usr.name, ub.session_commit("Changed denied columns of user {} to {}".format(usr.name,
usr.list_denied_column_values)) usr.list_denied_column_values()))
return "" return ""
@ -824,6 +963,7 @@ def pathchooser():
def basic_configuration(): def basic_configuration():
logout_user() logout_user()
if request.method == "POST": if request.method == "POST":
log.debug("Basic Configuration send")
return _configuration_update_helper(configured=filepicker) return _configuration_update_helper(configured=filepicker)
return _configuration_result(configured=filepicker) return _configuration_result(configured=filepicker)
@ -862,7 +1002,10 @@ def _configuration_gdrive_helper(to_save):
) )
# always show google drive settings, but in case of error deny support # always show google drive settings, but in case of error deny support
config.config_use_google_drive = (not gdrive_error) and ("config_use_google_drive" in to_save) new_gdrive_value = (not gdrive_error) and ("config_use_google_drive" in to_save)
if config.config_use_google_drive and not new_gdrive_value:
config.config_google_drive_watch_changes_response = {}
config.config_use_google_drive = new_gdrive_value
if _config_string(to_save, "config_google_drive_folder"): if _config_string(to_save, "config_google_drive_folder"):
gdriveutils.deleteDatabaseOnChange() gdriveutils.deleteDatabaseOnChange()
return gdrive_error return gdrive_error
@ -1079,7 +1222,8 @@ def _configuration_update_helper(configured):
return _configuration_result(unrar_status, gdrive_error, configured) return _configuration_result(unrar_status, gdrive_error, configured)
except (OperationalError, InvalidRequestError): except (OperationalError, InvalidRequestError):
ub.session.rollback() ub.session.rollback()
_configuration_result(_(u"Settings DB is not Writeable"), gdrive_error, configured) log.error("Settings DB is not Writeable")
_configuration_result(_("Settings DB is not Writeable"), gdrive_error, configured)
try: try:
metadata_db = os.path.join(config.config_calibre_dir, "metadata.db") metadata_db = os.path.join(config.config_calibre_dir, "metadata.db")
@ -1111,15 +1255,16 @@ def _configuration_result(error_flash=None, gdrive_error=None, configured=True):
if gdrive_error is None: if gdrive_error is None:
gdrive_error = gdriveutils.get_error_text() gdrive_error = gdriveutils.get_error_text()
if gdrive_error: if gdrive_error:
log.error(gdrive_error)
gdrive_error = _(gdrive_error) gdrive_error = _(gdrive_error)
else: else:
# if config.config_use_google_drive and\
if not gdrive_authenticate and gdrive_support: if not gdrive_authenticate and gdrive_support:
gdrivefolders = gdriveutils.listRootFolders() gdrivefolders = gdriveutils.listRootFolders()
show_back_button = current_user.is_authenticated show_back_button = current_user.is_authenticated
show_login_button = config.db_configured and not current_user.is_authenticated show_login_button = config.db_configured and not current_user.is_authenticated
if error_flash: if error_flash:
log.error(error_flash)
config.load() config.load()
flash(error_flash, category="error") flash(error_flash, category="error")
show_login_button = False show_login_button = False
@ -1172,30 +1317,46 @@ def _handle_new_user(to_save, content, languages, translations, kobo_support):
ub.session.add(content) ub.session.add(content)
ub.session.commit() ub.session.commit()
flash(_(u"User '%(user)s' created", user=content.name), category="success") flash(_(u"User '%(user)s' created", user=content.name), category="success")
log.debug("User {} created".format(content.name))
return redirect(url_for('admin.admin')) return redirect(url_for('admin.admin'))
except IntegrityError: except IntegrityError:
ub.session.rollback() ub.session.rollback()
flash(_(u"Found an existing account for this e-mail address or name."), category="error") log.error("Found an existing account for {} or {}".format(content.name, content.email))
flash(_("Found an existing account for this e-mail address or name."), category="error")
except OperationalError: except OperationalError:
ub.session.rollback() ub.session.rollback()
flash(_(u"Settings DB is not Writeable"), category="error") log.error("Settings DB is not Writeable")
flash(_("Settings DB is not Writeable"), category="error")
def _delete_user(content):
if ub.session.query(ub.User).filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN,
ub.User.id != content.id).count():
if content.name != "Guest":
ub.session.query(ub.User).filter(ub.User.id == content.id).delete()
ub.session_commit()
log.info(u"User {} deleted".format(content.name))
return(_(u"User '%(nick)s' deleted", nick=content.name))
else:
log.warning(_(u"Can't delete Guest User"))
raise Exception(_(u"Can't delete Guest User"))
else:
log.warning(u"No admin user remaining, can't delete user")
raise Exception(_(u"No admin user remaining, can't delete user"))
def _handle_edit_user(to_save, content, languages, translations, kobo_support): def _handle_edit_user(to_save, content, languages, translations, kobo_support):
if to_save.get("delete"): if to_save.get("delete"):
if ub.session.query(ub.User).filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN, try:
ub.User.id != content.id).count(): flash(_delete_user(content), category="success")
ub.session.query(ub.User).filter(ub.User.id == content.id).delete() except Exception as ex:
ub.session_commit() log.error(ex)
flash(_(u"User '%(nick)s' deleted", nick=content.name), category="success") flash(str(ex), category="error")
return redirect(url_for('admin.admin')) return redirect(url_for('admin.admin'))
else:
flash(_(u"No admin user remaining, can't delete user", nick=content.name), category="error")
return redirect(url_for('admin.admin'))
else: else:
if not ub.session.query(ub.User).filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN, if not ub.session.query(ub.User).filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN,
ub.User.id != content.id).count() and 'admin_role' not in to_save: ub.User.id != content.id).count() and 'admin_role' not in to_save:
flash(_(u"No admin user remaining, can't remove admin role", nick=content.name), category="error") log.warning("No admin user remaining, can't remove admin role from {}".format(content.name))
flash(_("No admin user remaining, can't remove admin role"), category="error")
return redirect(url_for('admin.admin')) return redirect(url_for('admin.admin'))
if to_save.get("password"): if to_save.get("password"):
content.password = generate_password_hash(to_save["password"]) content.password = generate_password_hash(to_save["password"])
@ -1235,6 +1396,7 @@ def _handle_edit_user(to_save, content, languages, translations, kobo_support):
if to_save.get("kindle_mail") != content.kindle_mail: if to_save.get("kindle_mail") != content.kindle_mail:
content.kindle_mail = valid_email(to_save["kindle_mail"]) if to_save["kindle_mail"] else "" content.kindle_mail = valid_email(to_save["kindle_mail"]) if to_save["kindle_mail"] else ""
except Exception as ex: except Exception as ex:
log.error(ex)
flash(str(ex), category="error") flash(str(ex), category="error")
return render_title_template("user_edit.html", return render_title_template("user_edit.html",
translations=translations, translations=translations,
@ -1249,12 +1411,15 @@ def _handle_edit_user(to_save, content, languages, translations, kobo_support):
try: try:
ub.session_commit() ub.session_commit()
flash(_(u"User '%(nick)s' updated", nick=content.name), category="success") flash(_(u"User '%(nick)s' updated", nick=content.name), category="success")
except IntegrityError: except IntegrityError as ex:
ub.session.rollback() ub.session.rollback()
flash(_(u"An unknown error occured."), category="error") log.error("An unknown error occurred while changing user: {}".format(str(ex)))
flash(_(u"An unknown error occurred."), category="error")
except OperationalError: except OperationalError:
ub.session.rollback() ub.session.rollback()
flash(_(u"Settings DB is not Writeable"), category="error") log.error("Settings DB is not Writeable")
flash(_("Settings DB is not Writeable"), category="error")
return ""
@admi.route("/admin/user/new", methods=["GET", "POST"]) @admi.route("/admin/user/new", methods=["GET", "POST"])
@ -1318,7 +1483,8 @@ def update_mailsettings():
config.save() config.save()
except (OperationalError, InvalidRequestError): except (OperationalError, InvalidRequestError):
ub.session.rollback() ub.session.rollback()
flash(_(u"Settings DB is not Writeable"), category="error") log.error("Settings DB is not Writeable")
flash(_("Settings DB is not Writeable"), category="error")
return edit_mailsettings() return edit_mailsettings()
if to_save.get("test"): if to_save.get("test"):
@ -1350,7 +1516,9 @@ def edit_user(user_id):
kobo_support = feature_support['kobo'] and config.config_kobo_sync kobo_support = feature_support['kobo'] and config.config_kobo_sync
if request.method == "POST": if request.method == "POST":
to_save = request.form.to_dict() to_save = request.form.to_dict()
_handle_edit_user(to_save, content, languages, translations, kobo_support) resp = _handle_edit_user(to_save, content, languages, translations, kobo_support)
if resp:
return resp
return render_title_template("user_edit.html", return render_title_template("user_edit.html",
translations=translations, translations=translations,
languages=languages, languages=languages,

View File

@ -71,7 +71,7 @@ if args.c:
if os.path.isfile(args.c): if os.path.isfile(args.c):
certfilepath = args.c certfilepath = args.c
else: else:
print("Certfilepath is invalid. Exiting...") print("Certfile path is invalid. Exiting...")
sys.exit(1) sys.exit(1)
if args.c == "": if args.c == "":
@ -81,7 +81,7 @@ if args.k:
if os.path.isfile(args.k): if os.path.isfile(args.k):
keyfilepath = args.k keyfilepath = args.k
else: else:
print("Keyfilepath is invalid. Exiting...") print("Keyfile path is invalid. Exiting...")
sys.exit(1) sys.exit(1)
if (args.k and not args.c) or (not args.k and args.c): if (args.k and not args.c) or (not args.k and args.c):
@ -91,29 +91,29 @@ if (args.k and not args.c) or (not args.k and args.c):
if args.k == "": if args.k == "":
keyfilepath = "" keyfilepath = ""
# handle and check ipadress argument # handle and check ip address argument
ipadress = args.i or None ip_address = args.i or None
if ipadress: if ip_address:
try: try:
# try to parse the given ip address with socket # try to parse the given ip address with socket
if hasattr(socket, 'inet_pton'): if hasattr(socket, 'inet_pton'):
if ':' in ipadress: if ':' in ip_address:
socket.inet_pton(socket.AF_INET6, ipadress) socket.inet_pton(socket.AF_INET6, ip_address)
else: else:
socket.inet_pton(socket.AF_INET, ipadress) socket.inet_pton(socket.AF_INET, ip_address)
else: else:
# on windows python < 3.4, inet_pton is not available # on windows python < 3.4, inet_pton is not available
# inet_atom only handles IPv4 addresses # inet_atom only handles IPv4 addresses
socket.inet_aton(ipadress) socket.inet_aton(ip_address)
except socket.error as err: except socket.error as err:
print(ipadress, ':', err) print(ip_address, ':', err)
sys.exit(1) sys.exit(1)
# handle and check user password argument # handle and check user password argument
user_credentials = args.s or None user_credentials = args.s or None
if user_credentials and ":" not in user_credentials: if user_credentials and ":" not in user_credentials:
print("No valid username:password format") print("No valid 'username:password' format")
sys.exit(3) sys.exit(3)
# Handles enableing of filepicker # Handles enabling of filepicker
filepicker = args.f or None filepicker = args.f or None

View File

@ -20,16 +20,18 @@
from __future__ import division, print_function, unicode_literals from __future__ import division, print_function, unicode_literals
import os import os
import sys import sys
import json
from sqlalchemy import exc, Column, String, Integer, SmallInteger, Boolean, BLOB, JSON from sqlalchemy import Column, String, Integer, SmallInteger, Boolean, BLOB, JSON
from sqlalchemy.exc import OperationalError from sqlalchemy.exc import OperationalError
from sqlalchemy.sql.expression import text
try: try:
# Compability with sqlalchemy 2.0 # Compatibility with sqlalchemy 2.0
from sqlalchemy.orm import declarative_base from sqlalchemy.orm import declarative_base
except ImportError: except ImportError:
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from . import constants, cli, logger, ub from . import constants, cli, logger
log = logger.create() log = logger.create()
@ -190,7 +192,7 @@ class _ConfigSQL(object):
@staticmethod @staticmethod
def get_config_ipaddress(): def get_config_ipaddress():
return cli.ipadress or "" return cli.ip_address or ""
def _has_role(self, role_flag): def _has_role(self, role_flag):
return constants.has_flag(self.config_default_role, role_flag) return constants.has_flag(self.config_default_role, role_flag)
@ -260,7 +262,6 @@ class _ConfigSQL(object):
""" """
new_value = dictionary.get(field, default) new_value = dictionary.get(field, default)
if new_value is None: if new_value is None:
# log.debug("_ConfigSQL set_from_dictionary field '%s' not found", field)
return False return False
if field not in self.__dict__: if field not in self.__dict__:
@ -277,7 +278,6 @@ class _ConfigSQL(object):
if current_value == new_value: if current_value == new_value:
return False return False
# log.debug("_ConfigSQL set_from_dictionary '%s' = %r (was %r)", field, new_value, current_value)
setattr(self, field, new_value) setattr(self, field, new_value)
return True return True
@ -358,7 +358,7 @@ def _migrate_table(session, orm_class):
if column_name[0] != '_': if column_name[0] != '_':
try: try:
session.query(column).first() session.query(column).first()
except exc.OperationalError as err: except OperationalError as err:
log.debug("%s: %s", column_name, err.args[0]) log.debug("%s: %s", column_name, err.args[0])
if column.default is not None: if column.default is not None:
if sys.version_info < (3, 0): if sys.version_info < (3, 0):
@ -368,20 +368,23 @@ def _migrate_table(session, orm_class):
column_default = "" column_default = ""
else: else:
if isinstance(column.default.arg, bool): if isinstance(column.default.arg, bool):
column_default = ("DEFAULT %r" % int(column.default.arg)) column_default = "DEFAULT {}".format(int(column.default.arg))
else: else:
column_default = ("DEFAULT '%r'" % column.default.arg) column_default = "DEFAULT `{}`".format(column.default.arg)
if isinstance(column.type, JSON): if isinstance(column.type, JSON):
column_type = "JSON" column_type = "JSON"
else: else:
column_type = column.type column_type = column.type
alter_table = "ALTER TABLE %s ADD COLUMN `%s` %s %s" % (orm_class.__tablename__, alter_table = text("ALTER TABLE %s ADD COLUMN `%s` %s %s" % (orm_class.__tablename__,
column_name, column_name,
column_type, column_type,
column_default) column_default))
log.debug(alter_table) log.debug(alter_table)
session.execute(alter_table) session.execute(alter_table)
changed = True changed = True
except json.decoder.JSONDecodeError as e:
log.error("Database corrupt column: {}".format(column_name))
log.debug(e)
if changed: if changed:
try: try:

View File

@ -33,7 +33,7 @@ from sqlalchemy.orm.collections import InstrumentedList
from sqlalchemy.ext.declarative import DeclarativeMeta from sqlalchemy.ext.declarative import DeclarativeMeta
from sqlalchemy.exc import OperationalError from sqlalchemy.exc import OperationalError
try: try:
# Compability with sqlalchemy 2.0 # Compatibility with sqlalchemy 2.0
from sqlalchemy.orm import declarative_base from sqlalchemy.orm import declarative_base
except ImportError: except ImportError:
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
@ -44,6 +44,7 @@ from flask_login import current_user
from babel import Locale as LC from babel import Locale as LC
from babel.core import UnknownLocaleError from babel.core import UnknownLocaleError
from flask_babel import gettext as _ from flask_babel import gettext as _
from flask import flash
from . import logger, ub, isoLanguages from . import logger, ub, isoLanguages
from .pagination import Pagination from .pagination import Pagination
@ -121,6 +122,8 @@ class Identifiers(Base):
return u"Douban" return u"Douban"
elif format_type == "goodreads": elif format_type == "goodreads":
return u"Goodreads" return u"Goodreads"
elif format_type == "babelio":
return u"Babelio"
elif format_type == "google": elif format_type == "google":
return u"Google Books" return u"Google Books"
elif format_type == "kobo": elif format_type == "kobo":
@ -148,6 +151,8 @@ class Identifiers(Base):
return u"https://dx.doi.org/{0}".format(self.val) return u"https://dx.doi.org/{0}".format(self.val)
elif format_type == "goodreads": elif format_type == "goodreads":
return u"https://www.goodreads.com/book/show/{0}".format(self.val) return u"https://www.goodreads.com/book/show/{0}".format(self.val)
elif format_type == "babelio":
return u"https://www.babelio.com/livres/titre/{0}".format(self.val)
elif format_type == "douban": elif format_type == "douban":
return u"https://book.douban.com/subject/{0}".format(self.val) return u"https://book.douban.com/subject/{0}".format(self.val)
elif format_type == "google": elif format_type == "google":
@ -393,7 +398,7 @@ class AlchemyEncoder(json.JSONEncoder):
if isinstance(o.__class__, DeclarativeMeta): if isinstance(o.__class__, DeclarativeMeta):
# an SQLAlchemy class # an SQLAlchemy class
fields = {} fields = {}
for field in [x for x in dir(o) if not x.startswith('_') and x != 'metadata']: for field in [x for x in dir(o) if not x.startswith('_') and x != 'metadata' and x!="password"]:
if field == 'books': if field == 'books':
continue continue
data = o.__getattribute__(field) data = o.__getattribute__(field)
@ -602,20 +607,46 @@ class CalibreDB():
neg_content_tags_filter = false() if negtags_list == [''] else Books.tags.any(Tags.name.in_(negtags_list)) neg_content_tags_filter = false() if negtags_list == [''] else Books.tags.any(Tags.name.in_(negtags_list))
pos_content_tags_filter = true() if postags_list == [''] else Books.tags.any(Tags.name.in_(postags_list)) pos_content_tags_filter = true() if postags_list == [''] else Books.tags.any(Tags.name.in_(postags_list))
if self.config.config_restricted_column: if self.config.config_restricted_column:
pos_cc_list = current_user.allowed_column_value.split(',') try:
pos_content_cc_filter = true() if pos_cc_list == [''] else \ pos_cc_list = current_user.allowed_column_value.split(',')
getattr(Books, 'custom_column_' + str(self.config.config_restricted_column)). \ pos_content_cc_filter = true() if pos_cc_list == [''] else \
any(cc_classes[self.config.config_restricted_column].value.in_(pos_cc_list)) getattr(Books, 'custom_column_' + str(self.config.config_restricted_column)). \
neg_cc_list = current_user.denied_column_value.split(',') any(cc_classes[self.config.config_restricted_column].value.in_(pos_cc_list))
neg_content_cc_filter = false() if neg_cc_list == [''] else \ neg_cc_list = current_user.denied_column_value.split(',')
getattr(Books, 'custom_column_' + str(self.config.config_restricted_column)). \ neg_content_cc_filter = false() if neg_cc_list == [''] else \
any(cc_classes[self.config.config_restricted_column].value.in_(neg_cc_list)) getattr(Books, 'custom_column_' + str(self.config.config_restricted_column)). \
any(cc_classes[self.config.config_restricted_column].value.in_(neg_cc_list))
except (KeyError, AttributeError):
pos_content_cc_filter = false()
neg_content_cc_filter = true()
log.error(u"Custom Column No.%d is not existing in calibre database",
self.config.config_restricted_column)
flash(_("Custom Column No.%(column)d is not existing in calibre database",
column=self.config.config_restricted_column),
category="error")
else: else:
pos_content_cc_filter = true() pos_content_cc_filter = true()
neg_content_cc_filter = false() neg_content_cc_filter = false()
return and_(lang_filter, pos_content_tags_filter, ~neg_content_tags_filter, return and_(lang_filter, pos_content_tags_filter, ~neg_content_tags_filter,
pos_content_cc_filter, ~neg_content_cc_filter, archived_filter) pos_content_cc_filter, ~neg_content_cc_filter, archived_filter)
@staticmethod
def get_checkbox_sorted(inputlist, state, offset, limit, order):
outcome = list()
elementlist = {ele.id: ele for ele in inputlist}
for entry in state:
try:
outcome.append(elementlist[entry])
except KeyError:
pass
del elementlist[entry]
for entry in elementlist:
outcome.append(elementlist[entry])
if order == "asc":
outcome.reverse()
return outcome[offset:offset + limit]
# Fill indexpage with all requested data from database # Fill indexpage with all requested data from database
def fill_indexpage(self, page, pagesize, database, db_filter, order, *join): def fill_indexpage(self, page, pagesize, database, db_filter, order, *join):
return self.fill_indexpage_with_archived_books(page, pagesize, database, db_filter, order, False, *join) return self.fill_indexpage_with_archived_books(page, pagesize, database, db_filter, order, False, *join)
@ -689,23 +720,33 @@ class CalibreDB():
return self.session.query(Books) \ return self.session.query(Books) \
.filter(and_(Books.authors.any(and_(*q)), func.lower(Books.title).ilike("%" + title + "%"))).first() .filter(and_(Books.authors.any(and_(*q)), func.lower(Books.title).ilike("%" + title + "%"))).first()
# read search results from calibre-database and return it (function is used for feed and simple search def search_query(self, term, *join):
def get_search_results(self, term, offset=None, order=None, limit=None):
order = order or [Books.sort]
pagination = None
term.strip().lower() term.strip().lower()
self.session.connection().connection.connection.create_function("lower", 1, lcase) self.session.connection().connection.connection.create_function("lower", 1, lcase)
q = list() q = list()
authorterms = re.split("[, ]+", term) authorterms = re.split("[, ]+", term)
for authorterm in authorterms: for authorterm in authorterms:
q.append(Books.authors.any(func.lower(Authors.name).ilike("%" + authorterm + "%"))) q.append(Books.authors.any(func.lower(Authors.name).ilike("%" + authorterm + "%")))
result = self.session.query(Books).filter(self.common_filters(True)).filter( query = self.session.query(Books)
if len(join) == 3:
query = query.outerjoin(join[0], join[1]).outerjoin(join[2])
elif len(join) == 2:
query = query.outerjoin(join[0], join[1])
elif len(join) == 1:
query = query.outerjoin(join[0])
return query.filter(self.common_filters(True)).filter(
or_(Books.tags.any(func.lower(Tags.name).ilike("%" + term + "%")), or_(Books.tags.any(func.lower(Tags.name).ilike("%" + term + "%")),
Books.series.any(func.lower(Series.name).ilike("%" + term + "%")), Books.series.any(func.lower(Series.name).ilike("%" + term + "%")),
Books.authors.any(and_(*q)), Books.authors.any(and_(*q)),
Books.publishers.any(func.lower(Publishers.name).ilike("%" + term + "%")), Books.publishers.any(func.lower(Publishers.name).ilike("%" + term + "%")),
func.lower(Books.title).ilike("%" + term + "%") func.lower(Books.title).ilike("%" + term + "%")
)).order_by(*order).all() ))
# read search results from calibre-database and return it (function is used for feed and simple search
def get_search_results(self, term, offset=None, order=None, limit=None, *join):
order = order or [Books.sort]
pagination = None
result = self.search_query(term, *join).order_by(*order).all()
result_count = len(result) result_count = len(result)
if offset != None and limit != None: if offset != None and limit != None:
offset = int(offset) offset = int(offset)

View File

@ -229,14 +229,14 @@ def modify_identifiers(input_identifiers, db_identifiers, db_session):
@editbook.route("/ajax/delete/<int:book_id>") @editbook.route("/ajax/delete/<int:book_id>")
@login_required @login_required
def delete_book_from_details(book_id): def delete_book_from_details(book_id):
return Response(delete_book(book_id,"", True), mimetype='application/json') return Response(delete_book(book_id, "", True), mimetype='application/json')
@editbook.route("/delete/<int:book_id>", defaults={'book_format': ""}) @editbook.route("/delete/<int:book_id>", defaults={'book_format': ""})
@editbook.route("/delete/<int:book_id>/<string:book_format>") @editbook.route("/delete/<int:book_id>/<string:book_format>")
@login_required @login_required
def delete_book_ajax(book_id, book_format): def delete_book_ajax(book_id, book_format):
return delete_book(book_id,book_format, False) return delete_book(book_id, book_format, False)
def delete_whole_book(book_id, book): def delete_whole_book(book_id, book):
@ -315,19 +315,19 @@ def delete_book(book_id, book_format, jsonResponse):
result, error = helper.delete_book(book, config.config_calibre_dir, book_format=book_format.upper()) result, error = helper.delete_book(book, config.config_calibre_dir, book_format=book_format.upper())
if not result: if not result:
if jsonResponse: if jsonResponse:
return json.dumps({"location": url_for("editbook.edit_book"), return json.dumps([{"location": url_for("editbook.edit_book", book_id=book_id),
"type": "alert", "type": "danger",
"format": "", "format": "",
"error": error}), "message": error}])
else: else:
flash(error, category="error") flash(error, category="error")
return redirect(url_for('editbook.edit_book', book_id=book_id)) return redirect(url_for('editbook.edit_book', book_id=book_id))
if error: if error:
if jsonResponse: if jsonResponse:
warning = {"location": url_for("editbook.edit_book"), warning = {"location": url_for("editbook.edit_book", book_id=book_id),
"type": "warning", "type": "warning",
"format": "", "format": "",
"error": error} "message": error}
else: else:
flash(error, category="warning") flash(error, category="warning")
if not book_format: if not book_format:
@ -339,6 +339,15 @@ def delete_book(book_id, book_format, jsonResponse):
except Exception as ex: except Exception as ex:
log.debug_or_exception(ex) log.debug_or_exception(ex)
calibre_db.session.rollback() calibre_db.session.rollback()
if jsonResponse:
return json.dumps([{"location": url_for("editbook.edit_book", book_id=book_id),
"type": "danger",
"format": "",
"message": ex}])
else:
flash(str(ex), category="error")
return redirect(url_for('editbook.edit_book', book_id=book_id))
else: else:
# book not found # book not found
log.error('Book with id "%s" could not be deleted: not found', book_id) log.error('Book with id "%s" could not be deleted: not found', book_id)
@ -1176,6 +1185,6 @@ def merge_list_book():
element.format, element.format,
element.uncompressed_size, element.uncompressed_size,
to_name)) to_name))
delete_book(from_book.id,"", True) # json_resp = delete_book(from_book.id,"", True)
return json.dumps({'success': True}) return json.dumps({'success': True})
return "" return ""

View File

@ -29,7 +29,7 @@ from sqlalchemy import Column, UniqueConstraint
from sqlalchemy import String, Integer from sqlalchemy import String, Integer
from sqlalchemy.orm import sessionmaker, scoped_session from sqlalchemy.orm import sessionmaker, scoped_session
try: try:
# Compability with sqlalchemy 2.0 # Compatibility with sqlalchemy 2.0
from sqlalchemy.orm import declarative_base from sqlalchemy.orm import declarative_base
except ImportError: except ImportError:
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
@ -221,7 +221,7 @@ def listRootFolders():
drive = getDrive(Gdrive.Instance().drive) drive = getDrive(Gdrive.Instance().drive)
folder = "'root' in parents and mimeType = 'application/vnd.google-apps.folder' and trashed = false" folder = "'root' in parents and mimeType = 'application/vnd.google-apps.folder' and trashed = false"
fileList = drive.ListFile({'q': folder}).GetList() fileList = drive.ListFile({'q': folder}).GetList()
except (ServerNotFoundError, ssl.SSLError) as e: except (ServerNotFoundError, ssl.SSLError, RefreshError) as e:
log.info("GDrive Error %s" % e) log.info("GDrive Error %s" % e)
fileList = [] fileList = []
return fileList return fileList
@ -257,7 +257,12 @@ def getEbooksFolderId(drive=None):
log.error('Error gDrive, root ID not found') log.error('Error gDrive, root ID not found')
gDriveId.path = '/' gDriveId.path = '/'
session.merge(gDriveId) session.merge(gDriveId)
session.commit() try:
session.commit()
except OperationalError as ex:
log.error("gdrive.db DB is not Writeable")
log.debug('Database error: %s', ex)
session.rollback()
return gDriveId.gdrive_id return gDriveId.gdrive_id
@ -272,37 +277,42 @@ def getFile(pathId, fileName, drive):
def getFolderId(path, drive): def getFolderId(path, drive):
# drive = getDrive(drive) # drive = getDrive(drive)
currentFolderId = getEbooksFolderId(drive) try:
sqlCheckPath = path if path[-1] == '/' else path + '/' currentFolderId = getEbooksFolderId(drive)
storedPathName = session.query(GdriveId).filter(GdriveId.path == sqlCheckPath).first() sqlCheckPath = path if path[-1] == '/' else path + '/'
storedPathName = session.query(GdriveId).filter(GdriveId.path == sqlCheckPath).first()
if not storedPathName: if not storedPathName:
dbChange = False dbChange = False
s = path.split('/') s = path.split('/')
for i, x in enumerate(s): for i, x in enumerate(s):
if len(x) > 0: if len(x) > 0:
currentPath = "/".join(s[:i+1]) currentPath = "/".join(s[:i+1])
if currentPath[-1] != '/': if currentPath[-1] != '/':
currentPath = currentPath + '/' currentPath = currentPath + '/'
storedPathName = session.query(GdriveId).filter(GdriveId.path == currentPath).first() storedPathName = session.query(GdriveId).filter(GdriveId.path == currentPath).first()
if storedPathName: if storedPathName:
currentFolderId = storedPathName.gdrive_id currentFolderId = storedPathName.gdrive_id
else:
currentFolder = getFolderInFolder(currentFolderId, x, drive)
if currentFolder:
gDriveId = GdriveId()
gDriveId.gdrive_id = currentFolder['id']
gDriveId.path = currentPath
session.merge(gDriveId)
dbChange = True
currentFolderId = currentFolder['id']
else: else:
currentFolderId = None currentFolder = getFolderInFolder(currentFolderId, x, drive)
break if currentFolder:
if dbChange: gDriveId = GdriveId()
session.commit() gDriveId.gdrive_id = currentFolder['id']
else: gDriveId.path = currentPath
currentFolderId = storedPathName.gdrive_id session.merge(gDriveId)
dbChange = True
currentFolderId = currentFolder['id']
else:
currentFolderId = None
break
if dbChange:
session.commit()
else:
currentFolderId = storedPathName.gdrive_id
except OperationalError as ex:
log.error("gdrive.db DB is not Writeable")
log.debug('Database error: %s', ex)
session.rollback()
return currentFolderId return currentFolderId
@ -346,7 +356,7 @@ def moveGdriveFolderRemote(origin_file, target_folder):
addParents=gFileTargetDir['id'], addParents=gFileTargetDir['id'],
removeParents=previous_parents, removeParents=previous_parents,
fields='id, parents').execute() fields='id, parents').execute()
# if previous_parents has no childs anymore, delete original fileparent # if previous_parents has no children anymore, delete original fileparent
if len(children['items']) == 1: if len(children['items']) == 1:
deleteDatabaseEntry(previous_parents) deleteDatabaseEntry(previous_parents)
drive.auth.service.files().delete(fileId=previous_parents).execute() drive.auth.service.files().delete(fileId=previous_parents).execute()
@ -507,9 +517,10 @@ def deleteDatabaseOnChange():
try: try:
session.query(GdriveId).delete() session.query(GdriveId).delete()
session.commit() session.commit()
except (OperationalError, InvalidRequestError): except (OperationalError, InvalidRequestError) as ex:
session.rollback() session.rollback()
log.info(u"GDrive DB is not Writeable") log.debug('Database error: %s', ex)
log.error(u"GDrive DB is not Writeable")
def updateGdriveCalibreFromLocal(): def updateGdriveCalibreFromLocal():
@ -524,13 +535,23 @@ def updateDatabaseOnEdit(ID,newPath):
storedPathName = session.query(GdriveId).filter(GdriveId.gdrive_id == ID).first() storedPathName = session.query(GdriveId).filter(GdriveId.gdrive_id == ID).first()
if storedPathName: if storedPathName:
storedPathName.path = sqlCheckPath storedPathName.path = sqlCheckPath
session.commit() try:
session.commit()
except OperationalError as ex:
log.error("gdrive.db DB is not Writeable")
log.debug('Database error: %s', ex)
session.rollback()
# Deletes the hashes in database of deleted book # Deletes the hashes in database of deleted book
def deleteDatabaseEntry(ID): def deleteDatabaseEntry(ID):
session.query(GdriveId).filter(GdriveId.gdrive_id == ID).delete() session.query(GdriveId).filter(GdriveId.gdrive_id == ID).delete()
session.commit() try:
session.commit()
except OperationalError as ex:
log.error("gdrive.db DB is not Writeable")
log.debug('Database error: %s', ex)
session.rollback()
# Gets cover file from gdrive # Gets cover file from gdrive
@ -547,7 +568,12 @@ def get_cover_via_gdrive(cover_path):
permissionAdded = PermissionAdded() permissionAdded = PermissionAdded()
permissionAdded.gdrive_id = df['id'] permissionAdded.gdrive_id = df['id']
session.add(permissionAdded) session.add(permissionAdded)
session.commit() try:
session.commit()
except OperationalError as ex:
log.error("gdrive.db DB is not Writeable")
log.debug('Database error: %s', ex)
session.rollback()
return df.metadata.get('webContentLink') return df.metadata.get('webContentLink')
else: else:
return None return None

View File

@ -693,6 +693,7 @@ def do_download_file(book, book_format, client, data, headers):
# ToDo Check headers parameter # ToDo Check headers parameter
for element in headers: for element in headers:
response.headers[element[0]] = element[1] response.headers[element[0]] = element[1]
log.info('Downloading file: {}'.format(os.path.join(filename, data.name + "." + book_format)))
return response return response
################################## ##################################
@ -732,7 +733,6 @@ def json_serial(obj):
'seconds': obj.seconds, 'seconds': obj.seconds,
'microseconds': obj.microseconds, 'microseconds': obj.microseconds,
} }
# return obj.isoformat()
raise TypeError("Type %s not serializable" % type(obj)) raise TypeError("Type %s not serializable" % type(obj))
@ -795,8 +795,8 @@ def tags_filters():
# checks if domain is in database (including wildcards) # checks if domain is in database (including wildcards)
# example SELECT * FROM @TABLE WHERE 'abcdefg' LIKE Name; # example SELECT * FROM @TABLE WHERE 'abcdefg' LIKE Name;
# from https://code.luasoftware.com/tutorials/flask/execute-raw-sql-in-flask-sqlalchemy/ # from https://code.luasoftware.com/tutorials/flask/execute-raw-sql-in-flask-sqlalchemy/
# in all calls the email address is checked for validity
def check_valid_domain(domain_text): def check_valid_domain(domain_text):
# domain_text = domain_text.split('@', 1)[-1].lower()
sql = "SELECT * FROM registration WHERE (:domain LIKE domain and allow = 1);" sql = "SELECT * FROM registration WHERE (:domain LIKE domain and allow = 1);"
result = ub.session.query(ub.Registration).from_statement(text(sql)).params(domain=domain_text).all() result = ub.session.query(ub.Registration).from_statement(text(sql)).params(domain=domain_text).all()
if not len(result): if not len(result):
@ -830,6 +830,7 @@ def get_download_link(book_id, book_format, client):
if book: if book:
data1 = calibre_db.get_book_format(book.id, book_format.upper()) data1 = calibre_db.get_book_format(book.id, book_format.upper())
else: else:
log.error("Book id {} not found for downloading".format(book_id))
abort(404) abort(404)
if data1: if data1:
# collect downloaded books only for registered user and not for anonymous user # collect downloaded books only for registered user and not for anonymous user

View File

@ -62,11 +62,11 @@ class _Logger(logging.Logger):
def debug_no_auth(self, message, *args, **kwargs): def debug_no_auth(self, message, *args, **kwargs):
message = message.strip("\r\n")
if message.startswith("send: AUTH"): if message.startswith("send: AUTH"):
self.debug(message[:16], stacklevel=2, *args, **kwargs) self.debug(message[:16], *args, **kwargs)
else: else:
self.debug(message, stacklevel=2, *args, **kwargs) self.debug(message, *args, **kwargs)
def get(name=None): def get(name=None):
@ -153,11 +153,11 @@ def setup(log_file, log_level=None):
file_handler.baseFilename = log_file file_handler.baseFilename = log_file
else: else:
try: try:
file_handler = RotatingFileHandler(log_file, maxBytes=50000, backupCount=2, encoding='utf-8') file_handler = RotatingFileHandler(log_file, maxBytes=100000, backupCount=2, encoding='utf-8')
except IOError: except IOError:
if log_file == DEFAULT_LOG_FILE: if log_file == DEFAULT_LOG_FILE:
raise raise
file_handler = RotatingFileHandler(DEFAULT_LOG_FILE, maxBytes=50000, backupCount=2, encoding='utf-8') file_handler = RotatingFileHandler(DEFAULT_LOG_FILE, maxBytes=100000, backupCount=2, encoding='utf-8')
log_file = "" log_file = ""
file_handler.setFormatter(FORMATTER) file_handler.setFormatter(FORMATTER)

View File

@ -30,6 +30,7 @@ from flask_babel import gettext as _
from flask_dance.consumer import oauth_authorized, oauth_error from flask_dance.consumer import oauth_authorized, oauth_error
from flask_dance.contrib.github import make_github_blueprint, github from flask_dance.contrib.github import make_github_blueprint, github
from flask_dance.contrib.google import make_google_blueprint, google from flask_dance.contrib.google import make_google_blueprint, google
from oauthlib.oauth2 import TokenExpiredError, InvalidGrantError
from flask_login import login_user, current_user, login_required from flask_login import login_user, current_user, login_required
from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.orm.exc import NoResultFound
@ -146,6 +147,7 @@ def bind_oauth_or_register(provider_id, provider_user_id, redirect_url, provider
ub.session.add(oauth_entry) ub.session.add(oauth_entry)
ub.session.commit() ub.session.commit()
flash(_(u"Link to %(oauth)s Succeeded", oauth=provider_name), category="success") flash(_(u"Link to %(oauth)s Succeeded", oauth=provider_name), category="success")
log.info("Link to {} Succeeded".format(provider_name))
return redirect(url_for('web.profile')) return redirect(url_for('web.profile'))
except Exception as ex: except Exception as ex:
log.debug_or_exception(ex) log.debug_or_exception(ex)
@ -194,6 +196,7 @@ def unlink_oauth(provider):
ub.session.commit() ub.session.commit()
logout_oauth_user() logout_oauth_user()
flash(_(u"Unlink to %(oauth)s Succeeded", oauth=oauth_check[provider]), category="success") flash(_(u"Unlink to %(oauth)s Succeeded", oauth=oauth_check[provider]), category="success")
log.info("Unlink to {} Succeeded".format(oauth_check[provider]))
except Exception as ex: except Exception as ex:
log.debug_or_exception(ex) log.debug_or_exception(ex)
ub.session.rollback() ub.session.rollback()
@ -257,11 +260,13 @@ if ub.oauth_support:
def github_logged_in(blueprint, token): def github_logged_in(blueprint, token):
if not token: if not token:
flash(_(u"Failed to log in with GitHub."), category="error") flash(_(u"Failed to log in with GitHub."), category="error")
log.error("Failed to log in with GitHub")
return False return False
resp = blueprint.session.get("/user") resp = blueprint.session.get("/user")
if not resp.ok: if not resp.ok:
flash(_(u"Failed to fetch user info from GitHub."), category="error") flash(_(u"Failed to fetch user info from GitHub."), category="error")
log.error("Failed to fetch user info from GitHub")
return False return False
github_info = resp.json() github_info = resp.json()
@ -273,11 +278,13 @@ if ub.oauth_support:
def google_logged_in(blueprint, token): def google_logged_in(blueprint, token):
if not token: if not token:
flash(_(u"Failed to log in with Google."), category="error") flash(_(u"Failed to log in with Google."), category="error")
log.error("Failed to log in with Google")
return False return False
resp = blueprint.session.get("/oauth2/v2/userinfo") resp = blueprint.session.get("/oauth2/v2/userinfo")
if not resp.ok: if not resp.ok:
flash(_(u"Failed to fetch user info from Google."), category="error") flash(_(u"Failed to fetch user info from Google."), category="error")
log.error("Failed to fetch user info from Google")
return False return False
google_info = resp.json() google_info = resp.json()
@ -318,11 +325,16 @@ if ub.oauth_support:
def github_login(): def github_login():
if not github.authorized: if not github.authorized:
return redirect(url_for('github.login')) return redirect(url_for('github.login'))
account_info = github.get('/user') try:
if account_info.ok: account_info = github.get('/user')
account_info_json = account_info.json() if account_info.ok:
return bind_oauth_or_register(oauthblueprints[0]['id'], account_info_json['id'], 'github.login', 'github') account_info_json = account_info.json()
flash(_(u"GitHub Oauth error, please retry later."), category="error") return bind_oauth_or_register(oauthblueprints[0]['id'], account_info_json['id'], 'github.login', 'github')
flash(_(u"GitHub Oauth error, please retry later."), category="error")
log.error("GitHub Oauth error, please retry later")
except (InvalidGrantError, TokenExpiredError) as e:
flash(_(u"GitHub Oauth error: {}").format(e), category="error")
log.error(e)
return redirect(url_for('web.login')) return redirect(url_for('web.login'))
@ -337,11 +349,16 @@ def github_login_unlink():
def google_login(): def google_login():
if not google.authorized: if not google.authorized:
return redirect(url_for("google.login")) return redirect(url_for("google.login"))
resp = google.get("/oauth2/v2/userinfo") try:
if resp.ok: resp = google.get("/oauth2/v2/userinfo")
account_info_json = resp.json() if resp.ok:
return bind_oauth_or_register(oauthblueprints[1]['id'], account_info_json['id'], 'google.login', 'google') account_info_json = resp.json()
flash(_(u"Google Oauth error, please retry later."), category="error") return bind_oauth_or_register(oauthblueprints[1]['id'], account_info_json['id'], 'google.login', 'google')
flash(_(u"Google Oauth error, please retry later."), category="error")
log.error("Google Oauth error, please retry later")
except (InvalidGrantError, TokenExpiredError) as e:
flash(_(u"Google Oauth error: {}").format(e), category="error")
log.error(e)
return redirect(url_for('web.login')) return redirect(url_for('web.login'))

View File

@ -546,8 +546,8 @@ def check_auth(username, password):
if bool(user and check_password_hash(str(user.password), password)): if bool(user and check_password_hash(str(user.password), password)):
return True return True
else: else:
ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr) ip_Address = request.headers.get('X-Forwarded-For', request.remote_addr)
log.warning('OPDS Login failed for user "%s" IP-address: %s', username.decode('utf-8'), ipAdress) log.warning('OPDS Login failed for user "%s" IP-address: %s', username.decode('utf-8'), ip_Address)
return False return False

View File

@ -49,5 +49,5 @@ except ImportError as err:
try: try:
from . import gmail from . import gmail
except ImportError as err: except ImportError as err:
log.debug("Cannot import Gmail, sending books via G-Mail Accounts will not work: %s", err) log.debug("Cannot import gmail, sending books via Gmail Oauth2 Verification will not work: %s", err)
gmail = None gmail = None

View File

@ -53,6 +53,7 @@ def setup_gmail(token):
'expiry': creds.expiry.isoformat(), 'expiry': creds.expiry.isoformat(),
'email': user_info 'email': user_info
} }
return {}
def get_user_info(credentials): def get_user_info(credentials):
user_info_service = build(serviceName='oauth2', version='v2',credentials=credentials) user_info_service = build(serviceName='oauth2', version='v2',credentials=credentials)
@ -60,6 +61,7 @@ def get_user_info(credentials):
return user_info.get('email', "") return user_info.get('email', "")
def send_messsage(token, msg): def send_messsage(token, msg):
log.debug("Start sending email via Gmail")
creds = Credentials( creds = Credentials(
token=token['token'], token=token['token'],
refresh_token=token['refresh_token'], refresh_token=token['refresh_token'],
@ -78,3 +80,4 @@ def send_messsage(token, msg):
body = {'raw': raw} body = {'raw': raw}
(service.users().messages().send(userId='me', body=body).execute()) (service.users().messages().send(userId='me', body=body).execute())
log.debug("Email send successfully via Gmail")

View File

@ -45,7 +45,7 @@ class ImprovedQueue(queue.Queue):
with self.mutex: with self.mutex:
return list(self.queue) return list(self.queue)
#Class for all worker tasks in the background # Class for all worker tasks in the background
class WorkerThread(threading.Thread): class WorkerThread(threading.Thread):
_instance = None _instance = None
@ -69,6 +69,7 @@ class WorkerThread(threading.Thread):
def add(cls, user, task): def add(cls, user, task):
ins = cls.getInstance() ins = cls.getInstance()
ins.num += 1 ins.num += 1
log.debug("Add Task for user: {}: {}".format(user, task))
ins.queue.put(QueuedTask( ins.queue.put(QueuedTask(
num=ins.num, num=ins.num,
user=user, user=user,

View File

@ -99,12 +99,14 @@ def add_to_shelf(shelf_id, book_id):
ub.session.commit() ub.session.commit()
except (OperationalError, InvalidRequestError): except (OperationalError, InvalidRequestError):
ub.session.rollback() ub.session.rollback()
log.error("Settings DB is not Writeable")
flash(_(u"Settings DB is not Writeable"), category="error") flash(_(u"Settings DB is not Writeable"), category="error")
if "HTTP_REFERER" in request.environ: if "HTTP_REFERER" in request.environ:
return redirect(request.environ["HTTP_REFERER"]) return redirect(request.environ["HTTP_REFERER"])
else: else:
return redirect(url_for('web.index')) return redirect(url_for('web.index'))
if not xhr: if not xhr:
log.debug("Book has been added to shelf: {}".format(shelf.name))
flash(_(u"Book has been added to shelf: %(sname)s", sname=shelf.name), category="success") flash(_(u"Book has been added to shelf: %(sname)s", sname=shelf.name), category="success")
if "HTTP_REFERER" in request.environ: if "HTTP_REFERER" in request.environ:
return redirect(request.environ["HTTP_REFERER"]) return redirect(request.environ["HTTP_REFERER"])
@ -123,6 +125,7 @@ def search_to_shelf(shelf_id):
return redirect(url_for('web.index')) return redirect(url_for('web.index'))
if not check_shelf_edit_permissions(shelf): if not check_shelf_edit_permissions(shelf):
log.warning("You are not allowed to add a book to the the shelf: {}".format(shelf.name))
flash(_(u"You are not allowed to add a book to the the shelf: %(name)s", name=shelf.name), category="error") flash(_(u"You are not allowed to add a book to the the shelf: %(name)s", name=shelf.name), category="error")
return redirect(url_for('web.index')) return redirect(url_for('web.index'))
@ -140,7 +143,7 @@ def search_to_shelf(shelf_id):
books_for_shelf = ub.searched_ids[current_user.id] books_for_shelf = ub.searched_ids[current_user.id]
if not books_for_shelf: if not books_for_shelf:
log.error("Books are already part of %s", shelf.name) log.error("Books are already part of {}".format(shelf.name))
flash(_(u"Books are already part of the shelf: %(name)s", name=shelf.name), category="error") flash(_(u"Books are already part of the shelf: %(name)s", name=shelf.name), category="error")
return redirect(url_for('web.index')) return redirect(url_for('web.index'))
@ -156,8 +159,10 @@ def search_to_shelf(shelf_id):
flash(_(u"Books have been added to shelf: %(sname)s", sname=shelf.name), category="success") flash(_(u"Books have been added to shelf: %(sname)s", sname=shelf.name), category="success")
except (OperationalError, InvalidRequestError): except (OperationalError, InvalidRequestError):
ub.session.rollback() ub.session.rollback()
flash(_(u"Settings DB is not Writeable"), category="error") log.error("Settings DB is not Writeable")
flash(_("Settings DB is not Writeable"), category="error")
else: else:
log.error("Could not add books to shelf: {}".format(shelf.name))
flash(_(u"Could not add books to shelf: %(sname)s", sname=shelf.name), category="error") flash(_(u"Could not add books to shelf: %(sname)s", sname=shelf.name), category="error")
return redirect(url_for('web.index')) return redirect(url_for('web.index'))
@ -168,7 +173,7 @@ def remove_from_shelf(shelf_id, book_id):
xhr = request.headers.get('X-Requested-With') == 'XMLHttpRequest' xhr = request.headers.get('X-Requested-With') == 'XMLHttpRequest'
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first() shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first()
if shelf is None: if shelf is None:
log.error("Invalid shelf specified: %s", shelf_id) log.error("Invalid shelf specified: {}".format(shelf_id))
if not xhr: if not xhr:
return redirect(url_for('web.index')) return redirect(url_for('web.index'))
return "Invalid shelf specified", 400 return "Invalid shelf specified", 400
@ -197,7 +202,8 @@ def remove_from_shelf(shelf_id, book_id):
ub.session.commit() ub.session.commit()
except (OperationalError, InvalidRequestError): except (OperationalError, InvalidRequestError):
ub.session.rollback() ub.session.rollback()
flash(_(u"Settings DB is not Writeable"), category="error") log.error("Settings DB is not Writeable")
flash(_("Settings DB is not Writeable"), category="error")
if "HTTP_REFERER" in request.environ: if "HTTP_REFERER" in request.environ:
return redirect(request.environ["HTTP_REFERER"]) return redirect(request.environ["HTTP_REFERER"])
else: else:
@ -211,6 +217,7 @@ def remove_from_shelf(shelf_id, book_id):
return "", 204 return "", 204
else: else:
if not xhr: if not xhr:
log.warning("You are not allowed to remove a book from shelf: {}".format(shelf.name))
flash(_(u"Sorry you are not allowed to remove a book from this shelf: %(sname)s", sname=shelf.name), flash(_(u"Sorry you are not allowed to remove a book from this shelf: %(sname)s", sname=shelf.name),
category="error") category="error")
return redirect(url_for('web.index')) return redirect(url_for('web.index'))
@ -258,7 +265,8 @@ def create_edit_shelf(shelf, title, page, shelf_id=False):
except (OperationalError, InvalidRequestError) as ex: except (OperationalError, InvalidRequestError) as ex:
ub.session.rollback() ub.session.rollback()
log.debug_or_exception(ex) log.debug_or_exception(ex)
flash(_(u"Settings DB is not Writeable"), category="error") log.error("Settings DB is not Writeable")
flash(_("Settings DB is not Writeable"), category="error")
except Exception as ex: except Exception as ex:
ub.session.rollback() ub.session.rollback()
log.debug_or_exception(ex) log.debug_or_exception(ex)
@ -278,6 +286,7 @@ def check_shelf_is_unique(shelf, to_save, shelf_id=False):
.first() is None .first() is None
if not is_shelf_name_unique: if not is_shelf_name_unique:
log.error("A public shelf with the name '{}' already exists.".format(to_save["title"]))
flash(_(u"A public shelf with the name '%(title)s' already exists.", title=to_save["title"]), flash(_(u"A public shelf with the name '%(title)s' already exists.", title=to_save["title"]),
category="error") category="error")
else: else:
@ -288,6 +297,7 @@ def check_shelf_is_unique(shelf, to_save, shelf_id=False):
.first() is None .first() is None
if not is_shelf_name_unique: if not is_shelf_name_unique:
log.error("A private shelf with the name '{}' already exists.".format(to_save["title"]))
flash(_(u"A private shelf with the name '%(title)s' already exists.", title=to_save["title"]), flash(_(u"A private shelf with the name '%(title)s' already exists.", title=to_save["title"]),
category="error") category="error")
return is_shelf_name_unique return is_shelf_name_unique
@ -311,7 +321,8 @@ def delete_shelf(shelf_id):
delete_shelf_helper(cur_shelf) delete_shelf_helper(cur_shelf)
except InvalidRequestError: except InvalidRequestError:
ub.session.rollback() ub.session.rollback()
flash(_(u"Settings DB is not Writeable"), category="error") log.error("Settings DB is not Writeable")
flash(_("Settings DB is not Writeable"), category="error")
return redirect(url_for('web.index')) return redirect(url_for('web.index'))
@ -345,7 +356,8 @@ def order_shelf(shelf_id):
ub.session.commit() ub.session.commit()
except (OperationalError, InvalidRequestError): except (OperationalError, InvalidRequestError):
ub.session.rollback() ub.session.rollback()
flash(_(u"Settings DB is not Writeable"), category="error") log.error("Settings DB is not Writeable")
flash(_("Settings DB is not Writeable"), category="error")
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first() shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first()
result = list() result = list()
@ -415,7 +427,8 @@ def render_show_shelf(shelf_type, shelf_id, page_no, sort_param):
ub.session.commit() ub.session.commit()
except (OperationalError, InvalidRequestError): except (OperationalError, InvalidRequestError):
ub.session.rollback() ub.session.rollback()
flash(_(u"Settings DB is not Writeable"), category="error") log.error("Settings DB is not Writeable")
flash(_("Settings DB is not Writeable"), category="error")
return render_title_template(page, return render_title_template(page,
entries=result, entries=result,

View File

@ -84,15 +84,24 @@ body {
#progress .bar-load, #progress .bar-load,
#progress .bar-read { #progress .bar-read {
display: flex; display: flex;
align-items: flex-end;
justify-content: flex-end;
position: absolute; position: absolute;
top: 0; top: 0;
left: 0;
bottom: 0; bottom: 0;
transition: width 150ms ease-in-out; transition: width 150ms ease-in-out;
} }
#progress .from-left {
left: 0;
align-items: flex-end;
justify-content: flex-end;
}
#progress .from-right {
right: 0;
align-items: flex-start;
justify-content: flex-start;
}
#progress .bar-load { #progress .bar-load {
color: #000; color: #000;
background-color: #ccc; background-color: #ccc;

View File

@ -15,6 +15,10 @@ body {
overflow: hidden; overflow: hidden;
} }
.myselect {
overflow: visible !important;
}
#main { #main {
position: absolute; position: absolute;
width: 100%; width: 100%;

View File

@ -22,7 +22,21 @@ $(function() {
}); });
$("#have_read_cb").on("change", function() { $("#have_read_cb").on("change", function() {
$(this).closest("form").submit(); $.post({
url: this.closest("form").action,
error: function(response) {
var data = [{type:"danger", message:response.responseText}]
$("#flash_success").remove();
$("#flash_danger").remove();
if (!jQuery.isEmptyObject(data)) {
data.forEach(function (item) {
$(".navbar").after('<div class="row-fluid text-center" style="margin-top: -20px;">' +
'<div id="flash_' + item.type + '" class="alert alert-' + item.type + '">' + item.message + '</div>' +
'</div>');
});
}
}
});
}); });
$(function() { $(function() {

View File

@ -171,7 +171,10 @@ kthoom.ImageFile = function(file) {
function initProgressClick() { function initProgressClick() {
$("#progress").click(function(e) { $("#progress").click(function(e) {
var page = Math.max(1, Math.ceil((e.offsetX / $(this).width()) * totalImages)) - 1; var offset = $(this).offset();
var x = e.pageX - offset.left;
var rate = settings.direction === 0 ? x / $(this).width() : 1 - x / $(this).width();
var page = Math.max(1, Math.ceil(rate * totalImages)) - 1;
currentImage = page; currentImage = page;
updatePage(); updatePage();
}); });
@ -285,6 +288,22 @@ function updatePage() {
} }
function updateProgress(loadPercentage) { function updateProgress(loadPercentage) {
if (settings.direction === 0) {
$("#progress .bar-read")
.removeClass("from-right")
.addClass("from-left");
$("#progress .bar-load")
.removeClass("from-right")
.addClass("from-left");
} else {
$("#progress .bar-read")
.removeClass("from-left")
.addClass("from-right");
$("#progress .bar-load")
.removeClass("from-left")
.addClass("from-right");
}
// Set the load/unzip progress if it's passed in // Set the load/unzip progress if it's passed in
if (loadPercentage) { if (loadPercentage) {
$("#progress .bar-load").css({ width: loadPercentage + "%" }); $("#progress .bar-load").css({ width: loadPercentage + "%" });
@ -526,18 +545,17 @@ function keyHandler(evt) {
break; break;
case kthoom.Key.SPACE: case kthoom.Key.SPACE:
var container = $("#mainContent"); var container = $("#mainContent");
var atTop = container.scrollTop() === 0; // var atTop = container.scrollTop() === 0;
var atBottom = container.scrollTop() >= container[0].scrollHeight - container.height(); // var atBottom = container.scrollTop() >= container[0].scrollHeight - container.height();
if (evt.shiftKey && atTop) { if (evt.shiftKey) {
evt.preventDefault(); evt.preventDefault();
// If it's Shift + Space and the container is at the top of the page // If it's Shift + Space and the container is at the top of the page
showLeftPage(); showPrevPage();
} else if (!evt.shiftKey && atBottom) { } else {
evt.preventDefault(); evt.preventDefault();
// If you're at the bottom of the page and you only pressed space // If you're at the bottom of the page and you only pressed space
showRightPage(); showNextPage();
container.scrollTop(0);
} }
break; break;
default: default:

View File

@ -114,18 +114,22 @@ $(document).ready(function() {
} }
}); });
$(".session").click(function() {
window.sessionStorage.setItem("back", window.location.pathname);
});
$("#back").click(function() {
var loc = sessionStorage.getItem("back");
if (!loc) {
loc = $(this).data("back");
}
sessionStorage.removeItem("back");
window.location.href = loc;
});
function confirmDialog(id, dialogid, dataValue, yesFn, noFn) { function confirmDialog(id, dialogid, dataValue, yesFn, noFn) {
var $confirm = $("#" + dialogid); var $confirm = $("#" + dialogid);
$confirm.modal('show');
$.ajax({
method:"get",
dataType: "json",
url: getPath() + "/ajax/loaddialogtexts/" + id,
success: function success(data) {
$("#header-"+ dialogid).html(data.header);
$("#text-"+ dialogid).html(data.main);
}
});
$("#btnConfirmYes-"+ dialogid).off('click').click(function () { $("#btnConfirmYes-"+ dialogid).off('click').click(function () {
yesFn(dataValue); yesFn(dataValue);
$confirm.modal("hide"); $confirm.modal("hide");
@ -136,16 +140,27 @@ function confirmDialog(id, dialogid, dataValue, yesFn, noFn) {
} }
$confirm.modal("hide"); $confirm.modal("hide");
}); });
$.ajax({
method:"get",
dataType: "json",
url: getPath() + "/ajax/loaddialogtexts/" + id,
success: function success(data) {
$("#header-"+ dialogid).html(data.header);
$("#text-"+ dialogid).html(data.main);
}
});
$confirm.modal('show');
} }
$("#delete_confirm").click(function() { $("#delete_confirm").click(function() {
//get data-id attribute of the clicked element //get data-id attribute of the clicked element
var deleteId = $(this).data("delete-id"); var deleteId = $(this).data("delete-id");
var bookFormat = $(this).data("delete-format"); var bookFormat = $(this).data("delete-format");
var ajaxResponse = $(this).data("ajax");
if (bookFormat) { if (bookFormat) {
window.location.href = getPath() + "/delete/" + deleteId + "/" + bookFormat; window.location.href = getPath() + "/delete/" + deleteId + "/" + bookFormat;
} else { } else {
if ($(this).data("delete-format")) { if (ajaxResponse) {
path = getPath() + "/ajax/delete/" + deleteId; path = getPath() + "/ajax/delete/" + deleteId;
$.ajax({ $.ajax({
method:"get", method:"get",
@ -163,6 +178,19 @@ $("#delete_confirm").click(function() {
} }
}); });
$("#books-table").bootstrapTable("refresh");
/*$.ajax({
method:"get",
url: window.location.pathname + "/../../ajax/listbooks",
async: true,
timeout: 900,
success:function(data) {
$("#book-table").bootstrapTable("load", data);
loadSuccess();
}
});*/
} }
}); });
} else { } else {
@ -187,6 +215,7 @@ $("#deleteModal").on("show.bs.modal", function(e) {
} }
$(e.currentTarget).find("#delete_confirm").data("delete-id", bookId); $(e.currentTarget).find("#delete_confirm").data("delete-id", bookId);
$(e.currentTarget).find("#delete_confirm").data("delete-format", bookfomat); $(e.currentTarget).find("#delete_confirm").data("delete-format", bookfomat);
$(e.currentTarget).find("#delete_confirm").data("ajax", $(e.relatedTarget).data("ajax"));
}); });

View File

@ -19,9 +19,9 @@
/* global getPath, confirmDialog */ /* global getPath, confirmDialog */
var selections = []; var selections = [];
var reload = false;
$(function() { $(function() {
$("#books-table").on("check.bs.table check-all.bs.table uncheck.bs.table uncheck-all.bs.table", $("#books-table").on("check.bs.table check-all.bs.table uncheck.bs.table uncheck-all.bs.table",
function (e, rowsAfter, rowsBefore) { function (e, rowsAfter, rowsBefore) {
var rows = rowsAfter; var rows = rowsAfter;
@ -117,6 +117,7 @@ $(function() {
$("#books-table").bootstrapTable({ $("#books-table").bootstrapTable({
sidePagination: "server", sidePagination: "server",
queryParams: queryParams,
pagination: true, pagination: true,
paginationLoop: false, paginationLoop: false,
paginationDetailHAlign: " hidden", paginationDetailHAlign: " hidden",
@ -176,7 +177,6 @@ $(function() {
}, },
}); });
$("#domain_allow_submit").click(function(event) { $("#domain_allow_submit").click(function(event) {
event.preventDefault(); event.preventDefault();
$("#domain_add_allow").ajaxForm(); $("#domain_add_allow").ajaxForm();
@ -318,7 +318,6 @@ $(function() {
}, },
url: getPath() + "/ajax/listrestriction/" + type + "/" + userId, url: getPath() + "/ajax/listrestriction/" + type + "/" + userId,
rowStyle: function(row) { rowStyle: function(row) {
// console.log('Reihe :' + row + " Index :" + index);
if (row.id.charAt(0) === "a") { if (row.id.charAt(0) === "a") {
return {classes: "bg-primary"}; return {classes: "bg-primary"};
} else { } else {
@ -387,7 +386,6 @@ $(function() {
var target = $(e.relatedTarget).attr('id'); var target = $(e.relatedTarget).attr('id');
var dataId; var dataId;
$(e.relatedTarget).one('focus', function(e){$(this).blur();}); $(e.relatedTarget).one('focus', function(e){$(this).blur();});
//$(e.relatedTarget).blur();
if ($(e.relatedTarget).hasClass("button_head")) { if ($(e.relatedTarget).hasClass("button_head")) {
dataId = $('#user-table').bootstrapTable('getSelections').map(a => a.id); dataId = $('#user-table').bootstrapTable('getSelections').map(a => a.id);
} else { } else {
@ -422,6 +420,7 @@ $(function() {
$("#user-table").bootstrapTable({ $("#user-table").bootstrapTable({
sidePagination: "server", sidePagination: "server",
queryParams: queryParams,
pagination: true, pagination: true,
paginationLoop: false, paginationLoop: false,
paginationDetailHAlign: " hidden", paginationDetailHAlign: " hidden",
@ -452,38 +451,13 @@ $(function() {
$(this).next().text(elText); $(this).next().text(elText);
}); });
}, },
onLoadSuccess: function () { onPostHeader () {
var guest = $(".editable[data-name='name'][data-value='Guest']"); move_header_elements();
guest.editable("disable");
$(".editable[data-name='locale'][data-pk='"+guest.data("pk")+"']").editable("disable");
$("input[data-name='admin_role'][data-pk='"+guest.data("pk")+"']").prop("disabled", true);
$("input[data-name='passwd_role'][data-pk='"+guest.data("pk")+"']").prop("disabled", true);
$("input[data-name='edit_shelf_role'][data-pk='"+guest.data("pk")+"']").prop("disabled", true);
// ToDo: Disable delete
}, },
onLoadSuccess: function () {
// eslint-disable-next-line no-unused-vars loadSuccess();
/*onEditableSave: function (field, row, oldvalue, $el) { },
if (field === "title" || field === "authors") { onColumnSwitch: function () {
$.ajax({
method:"get",
dataType: "json",
url: window.location.pathname + "/../../ajax/sort_value/" + field + "/" + row.id,
success: function success(data) {
var key = Object.keys(data)[0];
$("#books-table").bootstrapTable("updateCellByUniqueId", {
id: row.id,
field: key,
value: data[key]
});
// console.log(data);
}
});
}
},*/
// eslint-disable-next-line no-unused-vars
onColumnSwitch: function (field, checked) {
var visible = $("#user-table").bootstrapTable("getVisibleColumns"); var visible = $("#user-table").bootstrapTable("getVisibleColumns");
var hidden = $("#user-table").bootstrapTable("getHiddenColumns"); var hidden = $("#user-table").bootstrapTable("getHiddenColumns");
var st = ""; var st = "";
@ -501,31 +475,10 @@ $(function() {
url: window.location.pathname + "/../../ajax/user_table_settings", url: window.location.pathname + "/../../ajax/user_table_settings",
data: "{" + st + "}", data: "{" + st + "}",
}); });
handle_header_buttons();
}, },
}); });
$("#user_delete_selection").click(function() {
$("#user-table").bootstrapTable("uncheckAll");
});
function user_handle (userId) {
$.ajax({
method:"post",
url: window.location.pathname + "/../../ajax/deleteuser",
data: {"userid":userId}
});
$.ajax({
method:"get",
url: window.location.pathname + "/../../ajax/listusers",
async: true,
timeout: 900,
success:function(data) {
$("#user-table").bootstrapTable("load", data);
}
});
}
$("#user-table").on("click-cell.bs.table", function (field, value, row, $element) { $("#user-table").on("click-cell.bs.table", function (field, value, row, $element) {
if (value === "denied_column_value") { if (value === "denied_column_value") {
ConfirmDialog("btndeluser", "GeneralDeleteModal", $element.id, user_handle); ConfirmDialog("btndeluser", "GeneralDeleteModal", $element.id, user_handle);
@ -545,29 +498,39 @@ $(function() {
}); });
var func = $.inArray(e.type, ["check", "check-all"]) > -1 ? "union" : "difference"; var func = $.inArray(e.type, ["check", "check-all"]) > -1 ? "union" : "difference";
selections = window._[func](selections, ids); selections = window._[func](selections, ids);
if (selections.length < 1) { handle_header_buttons();
$("#user_delete_selection").addClass("disabled");
$("#user_delete_selection").attr("aria-disabled", true);
$(".check_head").attr("aria-disabled", true);
$(".check_head").attr("disabled", true);
$(".check_head").prop('checked', false);
$(".button_head").attr("aria-disabled", true);
$(".button_head").addClass("disabled");
$(".header_select").attr("disabled", true);
} else {
$("#user_delete_selection").removeClass("disabled");
$("#user_delete_selection").attr("aria-disabled", false);
$(".check_head").attr("aria-disabled", false);
$(".check_head").removeAttr("disabled");
$(".button_head").attr("aria-disabled", false);
$(".button_head").removeClass("disabled");
$(".header_select").removeAttr("disabled");
}
}); });
}); });
function handle_header_buttons () {
if (selections.length < 1) {
$("#user_delete_selection").addClass("disabled");
$("#user_delete_selection").attr("aria-disabled", true);
$(".check_head").attr("aria-disabled", true);
$(".check_head").attr("disabled", true);
$(".check_head").prop('checked', false);
$(".button_head").attr("aria-disabled", true);
$(".button_head").addClass("disabled");
$(".multi_head").attr("aria-disabled", true);
$(".multi_head").addClass("hidden");
$(".multi_selector").attr("aria-disabled", true);
$(".multi_selector").attr("disabled", true);
$(".header_select").attr("disabled", true);
} else {
$("#user_delete_selection").removeClass("disabled");
$("#user_delete_selection").attr("aria-disabled", false);
$(".check_head").attr("aria-disabled", false);
$(".check_head").removeAttr("disabled");
$(".button_head").attr("aria-disabled", false);
$(".button_head").removeClass("disabled");
$(".multi_head").attr("aria-disabled", false);
$(".multi_head").removeClass("hidden");
$(".multi_selector").attr("aria-disabled", false);
$(".multi_selector").removeAttr("disabled");
$('.multi_selector').selectpicker('refresh');
$(".header_select").removeAttr("disabled");
}
}
/* Function for deleting domain restrictions */ /* Function for deleting domain restrictions */
function TableActions (value, row) { function TableActions (value, row) {
return [ return [
@ -578,10 +541,6 @@ function TableActions (value, row) {
].join(""); ].join("");
} }
function editEntry(param)
{
console.log(param);
}
/* Function for deleting domain restrictions */ /* Function for deleting domain restrictions */
function RestrictionActions (value, row) { function RestrictionActions (value, row) {
return [ return [
@ -600,10 +559,10 @@ function EbookActions (value, row) {
].join(""); ].join("");
} }
/* Function for deleting books */ /* Function for deleting Users */
function UserActions (value, row) { function UserActions (value, row) {
return [ return [
"<div class=\"user-remove\" data-target=\"#GeneralDeleteModal\" title=\"Remove\">", "<div class=\"user-remove\" data-value=\"delete\" onclick=\"deleteUser(this, '" + row.id + "')\" data-pk=\"" + row.id + "\" title=\"Remove\">",
"<i class=\"glyphicon glyphicon-trash\"></i>", "<i class=\"glyphicon glyphicon-trash\"></i>",
"</div>" "</div>"
].join(""); ].join("");
@ -618,46 +577,160 @@ function responseHandler(res) {
} }
function singleUserFormatter(value, row) { function singleUserFormatter(value, row) {
return '<a class="btn btn-default" href="/admin/user/' + row.id + '">' + this.buttontext + '</a>' return '<a class="btn btn-default" onclick="storeLocation()" href="' + window.location.pathname + '/../../admin/user/' + row.id + '">' + this.buttontext + '</a>'
} }
function checkboxFormatter(value, row, index){ function checkboxFormatter(value, row, index){
if(value & this.column) if(value & this.column)
return '<input type="checkbox" class="chk" data-pk="' + row.id + '" data-name="' + this.name + '" checked onchange="checkboxChange(this, ' + row.id + ', \'' + this.field + '\', ' + this.column + ')">'; return '<input type="checkbox" class="chk" data-pk="' + row.id + '" data-name="' + this.field + '" checked onchange="checkboxChange(this, ' + row.id + ', \'' + this.name + '\', ' + this.column + ')">';
else else
return '<input type="checkbox" class="chk" data-pk="' + row.id + '" data-name="' + this.name + '" onchange="checkboxChange(this, ' + row.id + ', \'' + this.field + '\', ' + this.column + ')">'; return '<input type="checkbox" class="chk" data-pk="' + row.id + '" data-name="' + this.field + '" onchange="checkboxChange(this, ' + row.id + ', \'' + this.name + '\', ' + this.column + ')">';
}
/* Do some hiding disabling after user list is loaded */
function loadSuccess() {
var guest = $(".editable[data-name='name'][data-value='Guest']");
guest.editable("disable");
$("input:radio.check_head:checked").each(function() {
$(this).prop('checked', false);
});
$(".header_select").each(function() {
$(this).prop("selectedIndex", 0);
});
$(".header_select").each(function() {
$(this).prop("selectedIndex", 0);
});
$('.multi_selector').selectpicker('deselectAll');
$('.multi_selector').selectpicker('refresh');
$(".editable[data-name='locale'][data-pk='"+guest.data("pk")+"']").editable("disable");
$(".editable[data-name='locale'][data-pk='"+guest.data("pk")+"']").hide();
$("input[data-name='admin_role'][data-pk='"+guest.data("pk")+"']").prop("disabled", true);
$("input[data-name='passwd_role'][data-pk='"+guest.data("pk")+"']").prop("disabled", true);
$("input[data-name='edit_shelf_role'][data-pk='"+guest.data("pk")+"']").prop("disabled", true);
$("input[data-name='sidebar_read_and_unread'][data-pk='"+guest.data("pk")+"']").prop("disabled", true);
$(".user-remove[data-pk='"+guest.data("pk")+"']").hide();
}
function move_header_elements() {
$(".header_select").each(function () {
var item = $(this).parent();
var parent = item.parent().parent();
if (parent.prop('nodeName') === "TH") {
item.prependTo(parent);
}
});
$(".form-check").each(function () {
var item = $(this).parent();
var parent = item.parent().parent();
if (parent.prop('nodeName') === "TH") {
item.prependTo(parent);
}
});
$(".multi_select").each(function () {
var item = $(this);
var parent = item.parent().parent();
if (parent.prop('nodeName') === "TH") {
item.prependTo(parent);
item.addClass("myselect");
}
});
$(".multi_selector").selectpicker();
if (! $._data($(".multi_head").get(0), "events") ) {
// Functions have to be here, otherwise the callbacks are not fired if visible columns are changed
$(".multi_head").on("click", function () {
var val = $(this).data("set");
var field = $(this).data("name");
var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id);
var values = $("#" + field).val();
confirmDialog(
"restrictions",
"GeneralChangeModal",
0,
function () {
$.ajax({
method: "post",
url: window.location.pathname + "/../../ajax/editlistusers/" + field,
data: {"pk": result, "value": values, "action": val},
success: function (data) {
handleListServerResponse(data);
},
error: function (data) {
handleListServerResponse([{type: "danger", message: data.responseText}])
},
});
}
);
});
}
$("#user_delete_selection").click(function () {
$("#user-table").bootstrapTable("uncheckAll");
});
$("#select_locale").on("change", function () {
selectHeader(this, "locale");
});
$("#select_default_language").on("change", function () {
selectHeader(this, "default_language");
});
if (! $._data($(".check_head").get(0), "events") ) {
$(".check_head").on("change", function () {
var val = $(this).data("set");
var name = $(this).data("name");
var data = $(this).data("val");
checkboxHeader(val, name, data);
});
}
if (! $._data($(".button_head").get(0), "events") ) {
$(".button_head").on("click", function () {
var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id);
confirmDialog(
"btndeluser",
"GeneralDeleteModal",
0,
function () {
$.ajax({
method: "post",
url: window.location.pathname + "/../../ajax/deleteuser",
data: {"userid": result},
success: function (data) {
selections = selections.filter((el) => !result.includes(el));
handleListServerResponse(data);
},
error: function (data) {
handleListServerResponse([{type: "danger", message: data.responseText}])
},
});
}
);
});
}
}
function handleListServerResponse (data) {
$("#flash_success").remove();
$("#flash_danger").remove();
if (!jQuery.isEmptyObject(data)) {
data.forEach(function(item) {
$(".navbar").after('<div class="row-fluid text-center" style="margin-top: -20px;">' +
'<div id="flash_' + item.type + '" class="alert alert-' + item.type + '">' + item.message + '</div>' +
'</div>');
});
}
$("#user-table").bootstrapTable("refresh");
} }
function checkboxChange(checkbox, userId, field, field_index) { function checkboxChange(checkbox, userId, field, field_index) {
$.ajax({ $.ajax({
method:"post", method: "post",
url: window.location.pathname + "/../../ajax/editlistusers/" + field, url: window.location.pathname + "/../../ajax/editlistusers/" + field,
data: {"pk":userId, "field_index":field_index, "value": checkbox.checked} data: {"pk": userId, "field_index": field_index, "value": checkbox.checked},
/*<div className="editable-buttons"> error: function(data) {
<button type="button" className="btn btn-default btn-sm editable-cancel"><i handleListServerResponse([{type:"danger", message:data.responseText}])
className="glyphicon glyphicon-remove"></i></button> },
</div>*/ success: handleListServerResponse
/*<div className="editable-error-block help-block" style="">Text to show</div>*/
}); });
$.ajax({
method:"get",
url: window.location.pathname + "/../../ajax/listusers",
async: true,
timeout: 900,
success:function(data) {
$("#user-table").bootstrapTable("load", data);
}
});
}
function deactivateHeaderButtons(e) {
$("#user_delete_selection").addClass("disabled");
$("#user_delete_selection").attr("aria-disabled", true);
$(".check_head").attr("aria-disabled", true);
$(".check_head").attr("disabled", true);
$(".check_head").prop('checked', false);
$(".button_head").attr("aria-disabled", true);
$(".button_head").addClass("disabled");
$(".header_select").attr("disabled", true);
} }
function selectHeader(element, field) { function selectHeader(element, field) {
@ -668,19 +741,13 @@ function selectHeader(element, field) {
method: "post", method: "post",
url: window.location.pathname + "/../../ajax/editlistusers/" + field, url: window.location.pathname + "/../../ajax/editlistusers/" + field,
data: {"pk": result, "value": element.value}, data: {"pk": result, "value": element.value},
success: function () { error: function (data) {
$.ajax({ handleListServerResponse([{type:"danger", message:data.responseText}])
method: "get", },
url: window.location.pathname + "/../../ajax/listusers", success: handleListServerResponse,
async: true,
timeout: 900,
success: function (data) {
$("#user-table").bootstrapTable("load", data);
deactivateHeaderButtons();
}
});
}
}); });
},function() {
$(element).prop("selectedIndex", 0);
}); });
} }
} }
@ -692,46 +759,58 @@ function checkboxHeader(CheckboxState, field, field_index) {
method: "post", method: "post",
url: window.location.pathname + "/../../ajax/editlistusers/" + field, url: window.location.pathname + "/../../ajax/editlistusers/" + field,
data: {"pk": result, "field_index": field_index, "value": CheckboxState}, data: {"pk": result, "field_index": field_index, "value": CheckboxState},
success: function () { error: function (data) {
$.ajax({ handleListServerResponse([{type:"danger", message:data.responseText}])
method: "get", },
url: window.location.pathname + "/../../ajax/listusers", success: function (data) {
async: true, handleListServerResponse (data, true)
timeout: 900, },
success: function (data) { });
$("#user-table").bootstrapTable("load", data); },function() {
$("#user_delete_selection").addClass("disabled"); $("input:radio.check_head:checked").each(function() {
$("#user_delete_selection").attr("aria-disabled", true); $(this).prop('checked', false);
$(".check_head").attr("aria-disabled", true);
$(".check_head").attr("disabled", true);
$(".check_head").prop('checked', false);
$(".button_head").attr("aria-disabled", true);
$(".button_head").addClass("disabled");
$(".header_select").attr("disabled", true);
}
});
}
}); });
}); });
} }
function deleteUser(a,id){
confirmDialog(
"btndeluser",
"GeneralDeleteModal",
0,
function() {
$.ajax({
method:"post",
url: window.location.pathname + "/../../ajax/deleteuser",
data: {"userid":id},
success: function (data) {
userId = parseInt(id, 10);
selections = selections.filter(item => item !== userId);
handleListServerResponse(data);
},
error: function (data) {
handleListServerResponse([{type:"danger", message:data.responseText}])
},
});
}
);
}
function queryParams(params)
{
params.state = JSON.stringify(selections);
return params;
}
function storeLocation() {
window.sessionStorage.setItem("back", window.location.pathname);
}
function user_handle (userId) { function user_handle (userId) {
$.ajax({ $.ajax({
method:"post", method:"post",
url: window.location.pathname + "/../../ajax/deleteuser", url: window.location.pathname + "/../../ajax/deleteuser",
data: {"userid":userId} data: {"userid":userId}
}); });
$.ajax({ $("#user-table").bootstrapTable("refresh");
method:"get",
url: window.location.pathname + "/../../ajax/listusers",
async: true,
timeout: 900,
success:function(data) {
$("#user-table").bootstrapTable("load", data);
}
});
}
function test(){
console.log("hello");
} }

View File

@ -134,9 +134,9 @@ class TaskEmail(CalibreTask):
return message return message
def run(self, worker_thread): def run(self, worker_thread):
# create MIME message
msg = self.prepare_message()
try: try:
# create MIME message
msg = self.prepare_message()
if self.settings['mail_server_type'] == 0: if self.settings['mail_server_type'] == 0:
self.send_standard_email(msg) self.send_standard_email(msg)
else: else:
@ -173,6 +173,7 @@ class TaskEmail(CalibreTask):
org_smtpstderr = smtplib.stderr org_smtpstderr = smtplib.stderr
smtplib.stderr = logger.StderrLogger('worker.smtp') smtplib.stderr = logger.StderrLogger('worker.smtp')
log.debug("Start sending email")
if use_ssl == 2: if use_ssl == 2:
self.asyncSMTP = EmailSSL(self.settings["mail_server"], self.settings["mail_port"], self.asyncSMTP = EmailSSL(self.settings["mail_server"], self.settings["mail_port"],
timeout=timeout) timeout=timeout)
@ -195,11 +196,11 @@ class TaskEmail(CalibreTask):
self.asyncSMTP.sendmail(self.settings["mail_from"], self.recipent, fp.getvalue()) self.asyncSMTP.sendmail(self.settings["mail_from"], self.recipent, fp.getvalue())
self.asyncSMTP.quit() self.asyncSMTP.quit()
self._handleSuccess() self._handleSuccess()
log.debug("Email send successfully")
if sys.version_info < (3, 0): if sys.version_info < (3, 0):
smtplib.stderr = org_smtpstderr smtplib.stderr = org_smtpstderr
def send_gmail_email(self, message): def send_gmail_email(self, message):
return gmail.send_messsage(self.settings.get('mail_gmail_token', None), message) return gmail.send_messsage(self.settings.get('mail_gmail_token', None), message)
@ -258,3 +259,6 @@ class TaskEmail(CalibreTask):
@property @property
def name(self): def name(self):
return "Email" return "Email"
def __str__(self):
return "{}, {}".format(self.name, self.subject)

View File

@ -26,7 +26,7 @@
{% for user in allUser %} {% for user in allUser %}
{% if not user.role_anonymous() or config.config_anonbrowse %} {% if not user.role_anonymous() or config.config_anonbrowse %}
<tr> <tr>
<td><a href="{{url_for('admin.edit_user', user_id=user.id)}}">{{user.name}}</a></td> <td><a class="session" href="{{url_for('admin.edit_user', user_id=user.id)}}">{{user.name}}</a></td>
<td>{{user.email}}</td> <td>{{user.email}}</td>
<td>{{user.kindle_mail}}</td> <td>{{user.kindle_mail}}</td>
<td>{{user.downloads.count()}}</td> <td>{{user.downloads.count()}}</td>

View File

@ -1,6 +1,7 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% macro text_table_row(parameter, edit_text, show_text, validate) -%} {% macro text_table_row(parameter, edit_text, show_text, validate, sort) -%}
<th data-field="{{ parameter }}" id="{{ parameter }}" data-sortable="true" <th data-field="{{ parameter }}" id="{{ parameter }}"
{% if sort %}data-sortable="true" {% endif %}
data-visible = "{{visiblility.get(parameter)}}" data-visible = "{{visiblility.get(parameter)}}"
{% if g.user.role_edit() %} {% if g.user.role_edit() %}
data-editable-type="text" data-editable-type="text"
@ -43,16 +44,16 @@
<th data-field="state" data-checkbox="true" data-sortable="true"></th> <th data-field="state" data-checkbox="true" data-sortable="true"></th>
{% endif %} {% endif %}
<th data-field="id" id="id" data-visible="false" data-switchable="false"></th> <th data-field="id" id="id" data-visible="false" data-switchable="false"></th>
{{ text_table_row('title', _('Enter Title'),_('Title'), true) }} {{ text_table_row('title', _('Enter Title'),_('Title'), true, true) }}
{{ text_table_row('sort', _('Enter Title Sort'),_('Title Sort'), false) }} {{ text_table_row('sort', _('Enter Title Sort'),_('Title Sort'), false, true) }}
{{ text_table_row('author_sort', _('Enter Author Sort'),_('Author Sort'), false) }} {{ text_table_row('author_sort', _('Enter Author Sort'),_('Author Sort'), false, true) }}
{{ text_table_row('authors', _('Enter Authors'),_('Authors'), true) }} {{ text_table_row('authors', _('Enter Authors'),_('Authors'), true, true) }}
{{ text_table_row('tags', _('Enter Categories'),_('Categories'), false) }} {{ text_table_row('tags', _('Enter Categories'),_('Categories'), false, true) }}
{{ text_table_row('series', _('Enter Series'),_('Series'), false) }} {{ text_table_row('series', _('Enter Series'),_('Series'), false, true) }}
<th data-field="series_index" id="series_index" data-visible="{{visiblility.get('series_index')}}" data-edit-validate="{{ _('This Field is Required') }}" data-sortable="true" {% if g.user.role_edit() %} data-editable-type="number" data-editable-placeholder="1" data-editable-step="0.01" data-editable-min="0" data-editable-url="{{ url_for('editbook.edit_list_book', param='series_index')}}" data-edit="true" data-editable-title="{{_('Enter title')}}"{% endif %}>{{_('Series Index')}}</th> <th data-field="series_index" id="series_index" data-visible="{{visiblility.get('series_index')}}" data-edit-validate="{{ _('This Field is Required') }}" data-sortable="true" {% if g.user.role_edit() %} data-editable-type="number" data-editable-placeholder="1" data-editable-step="0.01" data-editable-min="0" data-editable-url="{{ url_for('editbook.edit_list_book', param='series_index')}}" data-edit="true" data-editable-title="{{_('Enter title')}}"{% endif %}>{{_('Series Index')}}</th>
{{ text_table_row('languages', _('Enter Languages'),_('Languages'), false) }} {{ text_table_row('languages', _('Enter Languages'),_('Languages'), false, true) }}
<!--th data-field="pubdate" data-type="date" data-visible="{{visiblility.get('pubdate')}}" data-viewformat="dd.mm.yyyy" id="pubdate" data-sortable="true">{{_('Publishing Date')}}</th--> <!--th data-field="pubdate" data-type="date" data-visible="{{visiblility.get('pubdate')}}" data-viewformat="dd.mm.yyyy" id="pubdate" data-sortable="true">{{_('Publishing Date')}}</th-->
{{ text_table_row('publishers', _('Enter Publishers'),_('Publishers'), false) }} {{ text_table_row('publishers', _('Enter Publishers'),_('Publishers'), false, true) }}
{% if g.user.role_delete_books() and g.user.role_edit()%} {% if g.user.role_delete_books() and g.user.role_edit()%}
<th data-align="right" data-formatter="EbookActions" data-switchable="false">{{_('Delete')}}</th> <th data-align="right" data-formatter="EbookActions" data-switchable="false">{{_('Delete')}}</th>
{% endif %} {% endif %}

View File

@ -12,7 +12,7 @@
<label for="mail_server_type">{{_('Choose Server Type')}}</label> <label for="mail_server_type">{{_('Choose Server Type')}}</label>
<select name="mail_server_type" id="config_email_type" class="form-control" data-control="email-settings"> <select name="mail_server_type" id="config_email_type" class="form-control" data-control="email-settings">
<option value="0" {% if content.mail_server_type == 0 %}selected{% endif %}>{{_('Use Standard E-Mail Account')}}</option> <option value="0" {% if content.mail_server_type == 0 %}selected{% endif %}>{{_('Use Standard E-Mail Account')}}</option>
<option value="1" {% if content.mail_server_type == 1 %}selected{% endif %}>{{_('G-Mail Account with OAuth2 Verfification')}}</option> <option value="1" {% if content.mail_server_type == 1 %}selected{% endif %}>{{_('Gmail Account with OAuth2 Verfification')}}</option>
</select> </select>
</div> </div>
<div data-related="email-settings-1"> <div data-related="email-settings-1">
@ -20,7 +20,7 @@
{% if content.mail_gmail_token == {} %} {% if content.mail_gmail_token == {} %}
<button type="submit" id="gmail_server" name="gmail" value="submit" class="btn btn-default">{{_('Setup Gmail Account as E-Mail Server')}}</button> <button type="submit" id="gmail_server" name="gmail" value="submit" class="btn btn-default">{{_('Setup Gmail Account as E-Mail Server')}}</button>
{% else %} {% else %}
<button type="submit" id="invalidate_server" name="invalidate" value="submit" class="btn btn-danger">{{_('Revoke G-Mail Access')}}</button> <button type="submit" id="invalidate_server" name="invalidate" value="submit" class="btn btn-danger">{{_('Revoke Gmail Access')}}</button>
{% endif %} {% endif %}
</div> </div>
</div> </div>
@ -66,7 +66,7 @@
{% if feature_support['gmail'] %} {% if feature_support['gmail'] %}
</div> </div>
{% endif %} {% endif %}
<a href="{{ url_for('admin.admin') }}" id="back" class="btn btn-default">{{_('Back')}}</a> <a href="{{ url_for('admin.admin') }}" id="email_back" class="btn btn-default">{{_('Back')}}</a>
</form> </form>
{% if g.allow_registration %} {% if g.allow_registration %}
<div class="col-md-10 col-lg-6"> <div class="col-md-10 col-lg-6">

View File

@ -92,7 +92,7 @@
{% for message in get_flashed_messages(with_categories=True) %} {% for message in get_flashed_messages(with_categories=True) %}
{%if message[0] == "error" %} {%if message[0] == "error" %}
<div class="row-fluid text-center" style="margin-top: -20px;"> <div class="row-fluid text-center" style="margin-top: -20px;">
<div id="flash_alert" class="alert alert-danger">{{ message[1] }}</div> <div id="flash_danger" class="alert alert-danger">{{ message[1] }}</div>
</div> </div>
{%endif%} {%endif%}
{%if message[0] == "info" %} {%if message[0] == "info" %}

View File

@ -60,12 +60,12 @@
<a id="fullscreen" class="icon-resize-full">Fullscreen</a> <a id="fullscreen" class="icon-resize-full">Fullscreen</a>
</div> </div>
<div id="progress" role="progressbar" class="loading"> <div id="progress" role="progressbar" class="loading">
<div class="bar-load"> <div class="bar-load from-left">
<div class="text load"> <div class="text load">
Loading... Loading...
</div> </div>
</div> </div>
<div class="bar-read"> <div class="bar-read from-left">
<div class="text page"></div> <div class="text page"></div>
</div> </div>
</div> </div>

View File

@ -129,7 +129,7 @@
<div class="col-sm-12"> <div class="col-sm-12">
<div id="user_submit" class="btn btn-default">{{_('Save')}}</div> <div id="user_submit" class="btn btn-default">{{_('Save')}}</div>
{% if not profile %} {% if not profile %}
<a href="{{ url_for('admin.admin') }}" id="back" class="btn btn-default">{{_('Cancel')}}</a> <div class="btn btn-default" data-back="{{ url_for('admin.admin') }}" id="back">{{_('Cancel')}}</div>
{% endif %} {% endif %}
{% if g.user and g.user.role_admin() and not profile and not new_user and not content.role_anonymous() %} {% if g.user and g.user.role_admin() and not profile and not new_user and not content.role_anonymous() %}
<div class="btn btn-danger" id="btndeluser" data-value="{{ content.id }}" data-remote="false" >{{_('Delete User')}}</div> <div class="btn btn-danger" id="btndeluser" data-value="{{ content.id }}" data-remote="false" >{{_('Delete User')}}</div>

View File

@ -1,5 +1,5 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% macro user_table_row(parameter, edit_text, show_text, validate, button=False, id=0) -%} {% macro user_table_row(parameter, edit_text, show_text, validate, elements=False) -%}
<th data-field="{{ parameter }}" id="{{ parameter }}" <th data-field="{{ parameter }}" id="{{ parameter }}"
data-name="{{ parameter }}" data-name="{{ parameter }}"
data-visible="{{visiblility.get(parameter)}}" data-visible="{{visiblility.get(parameter)}}"
@ -11,8 +11,22 @@
data-sortable="true" data-sortable="true"
{% endif %} {% endif %}
{% if validate %}data-edit-validate="{{ _('This Field is Required') }}"{% endif %}> {% if validate %}data-edit-validate="{{ _('This Field is Required') }}"{% endif %}>
{% if button %} {% if elements %}
<!--div><button data-id="{{id}}" data-toggle="modal" data-target="#restrictModal" class="btn btn-default button_head disabled" aria-disabled="true">{{edit_text}}</button></div--><br> <div class="multi_select">
<select class="multi_selector" id="{{ parameter }}" data-live-search="true" data-style="btn-default" data-dropup-auto="false" aria-disabled="true" multiple disabled>
{% for tag in elements %}
<option class="tags_click" value="{{tag.id}}">{% if tag.name %}{{tag.name}}{% else %}{{tag.value}}{% endif %}</option>
{% endfor %}
</select>
<div class="btn-group btn-group-justified" role="group">
<div class="btn-group" role="group">
<div class="multi_head btn btn-default hidden" data-set="remove" data-name="{{parameter}}" aria-disabled="true">{{_('Remove')}}</div>
</div>
<div class="btn-group" role="group">
<div class="multi_head btn btn-default hidden" data-set="add" data-name="{{parameter}}" aria-disabled="true">{{_('Add')}}</div>
</div>
</div>
</div>
{% endif %} {% endif %}
{{ show_text }} {{ show_text }}
</th> </th>
@ -23,15 +37,17 @@
data-visible="{{element.get(array_field)}}" data-visible="{{element.get(array_field)}}"
data-column="{{value.get(array_field)}}" data-column="{{value.get(array_field)}}"
data-formatter="checkboxFormatter"> data-formatter="checkboxFormatter">
<div class="form-check"> <div class="form-check">
<label> <div>
<input type="radio" class="check_head" name="options_{{array_field}}" onchange="checkboxHeader('false', '{{parameter}}', {{value.get(array_field)}})" disabled>{{_('Deny')}}
</label> <input type="radio" class="check_head" data-set="false" data-val={{value.get(array_field)}} name="options_{{array_field}}" id="false_{{array_field}}" data-name="{{parameter}}" disabled>{{_('Deny')}}
</div>
<div>
<input type="radio" class="check_head" data-set="true" data-val={{value.get(array_field)}} name="options_{{array_field}}" data-name="{{parameter}}" disabled>{{_('Allow')}}
</div> </div>
<div class="form-check">
<label>
<input type="radio" class="check_head" name="options_{{array_field}}" onchange="checkboxHeader('true', '{{parameter}}', {{value.get(array_field)}})" disabled>{{_('Allow')}}
</label>
</div> </div>
{{show_text}} {{show_text}}
</th> </th>
@ -48,14 +64,14 @@
data-editable-source={{url}} data-editable-source={{url}}
{% if validate %}data-edit-validate="{{ _('This Field is Required') }}"{% endif %}> {% if validate %}data-edit-validate="{{ _('This Field is Required') }}"{% endif %}>
<div> <div>
<select id="select_{{ parameter }}" class="header_select" onchange="selectHeader(this, '{{parameter}}')" disabled=""> <select id="select_{{ parameter }}" class="header_select" disabled="">
<option value="none">{{ _('Select...') }}</option>
<option value="all">{{ _('Show All') }}</option> <option value="all">{{ _('Show All') }}</option>
{% for language in languages %} {% for language in languages %}
<option value="{{language.lang_code}}">{{language.name}}</option> <option value="{{language.lang_code}}">{{language.name}}</option>
{% endfor %} {% endfor %}
</select> </select>
</div><br> </div>
{{ show_text }} {{ show_text }}
</th> </th>
{%- endmacro %} {%- endmacro %}
@ -71,13 +87,13 @@
data-editable-source={{url}} data-editable-source={{url}}
{% if validate %}data-edit-validate="{{ _('This Field is Required') }}"{% endif %}> {% if validate %}data-edit-validate="{{ _('This Field is Required') }}"{% endif %}>
<div> <div>
<select id="select_{{ parameter }}" class="header_select" onchange="selectHeader(this, '{{parameter}}')" disabled=""> <select id="select_{{ parameter }}" class="header_select" disabled="">
<option value="None">{{_('Select...')}}</option> <option value="None">{{_('Select...')}}</option>
{% for translation in translations %} {% for translation in translations %}
<option value="{{translation}}">{{translation.display_name|capitalize}}</option> <option value="{{translation}}">{{translation.display_name|capitalize}}</option>
{% endfor %} {% endfor %}
</select> </select>
</div><br> </div>
{{ show_text }} {{ show_text }}
</th> </th>
{%- endmacro %} {%- endmacro %}
@ -86,6 +102,7 @@
{% block header %} {% block header %}
<link href="{{ url_for('static', filename='css/libs/bootstrap-table.min.css') }}" rel="stylesheet"> <link href="{{ url_for('static', filename='css/libs/bootstrap-table.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/libs/bootstrap-editable.css') }}" rel="stylesheet"> <link href="{{ url_for('static', filename='css/libs/bootstrap-editable.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/libs/bootstrap-select.min.css') }}" rel="stylesheet" >
{% endblock %} {% endblock %}
{% block body %} {% block body %}
<h2 class="{{page}}">{{_(title)}}</h2> <h2 class="{{page}}">{{_(title)}}</h2>
@ -106,20 +123,21 @@
{{ user_table_row('kindle_mail', _('Enter Kindle E-mail Address'), _('Kindle E-mail'), false) }} {{ user_table_row('kindle_mail', _('Enter Kindle E-mail Address'), _('Kindle E-mail'), false) }}
{{ user_select_translations('locale', url_for('admin.table_get_locale'), _('Locale'), true) }} {{ user_select_translations('locale', url_for('admin.table_get_locale'), _('Locale'), true) }}
{{ user_select_languages('default_language', url_for('admin.table_get_default_lang'), _('Visible Book Languages'), true) }} {{ user_select_languages('default_language', url_for('admin.table_get_default_lang'), _('Visible Book Languages'), true) }}
{{ user_table_row('denied_tags', _("Edit Denied Tags"), _("Denied Tags"), false, true, 0) }} {{ user_table_row('denied_tags', _("Edit Denied Tags"), _("Denied Tags"), false, tags) }}
{{ user_table_row('allowed_tags', _("Edit Allowed Tags"), _("Allowed Tags"), false, true, 1) }} {{ user_table_row('allowed_tags', _("Edit Allowed Tags"), _("Allowed Tags"), false, tags) }}
{{ user_table_row('allowed_column_value', _("Edit Allowed Column Values"), _("Allowed Column Values"), false, true, 2) }} {{ user_table_row('allowed_column_value', _("Edit Allowed Column Values"), _("Allowed Column Values"), false, custom_values) }}
{{ user_table_row('denied_column_value', _("Edit Denied Column Values"), _("Denied Columns Values"), false, true, 3) }} {{ user_table_row('denied_column_value', _("Edit Denied Column Values"), _("Denied Columns Values"), false, custom_values) }}
{{ user_checkbox_row("role", "admin_role", _('Admin'), visiblility, all_roles)}} {{ user_checkbox_row("role", "admin_role", _('Admin'), visiblility, all_roles)}}
{{ user_checkbox_row("role", "download_role",_('Upload'), visiblility, all_roles)}}
{{ user_checkbox_row("role", "upload_role", _('Download'), visiblility, all_roles)}}
{{ user_checkbox_row("role", "edit_role", _('Edit'), visiblility, all_roles)}}
{{ user_checkbox_row("role", "passwd_role", _('Change Password'), visiblility, all_roles)}} {{ user_checkbox_row("role", "passwd_role", _('Change Password'), visiblility, all_roles)}}
{{ user_checkbox_row("role", "edit_shelf_role", _('Edit Public Shelfs'), visiblility, all_roles)}} {{ user_checkbox_row("role", "upload_role",_('Upload'), visiblility, all_roles)}}
{{ user_checkbox_row("role", "delete_role", _('Delete'), visiblility, all_roles)}} {{ user_checkbox_row("role", "download_role", _('Download'), visiblility, all_roles)}}
{{ user_checkbox_row("role", "viewer_role", _('View'), visiblility, all_roles)}} {{ user_checkbox_row("role", "viewer_role", _('View'), visiblility, all_roles)}}
{{ user_checkbox_row("role", "edit_role", _('Edit'), visiblility, all_roles)}}
{{ user_checkbox_row("role", "delete_role", _('Delete'), visiblility, all_roles)}}
{{ user_checkbox_row("role", "edit_shelf_role", _('Edit Public Shelfs'), visiblility, all_roles)}}
{{ user_checkbox_row("sidebar_view", "detail_random", _('Show Random Books in Detail View'), visiblility, sidebar_settings)}} {{ user_checkbox_row("sidebar_view", "detail_random", _('Show Random Books in Detail View'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_language", _('Show language selection'), visiblility, sidebar_settings)}} {{ user_checkbox_row("sidebar_view", "sidebar_language", _('Show language selection'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_read_and_unread", _('Show read/unread selection'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_series", _('Show series selection'), visiblility, sidebar_settings)}} {{ user_checkbox_row("sidebar_view", "sidebar_series", _('Show series selection'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_category", _('Show category selection'), visiblility, sidebar_settings)}} {{ user_checkbox_row("sidebar_view", "sidebar_category", _('Show category selection'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_random", _('Show random books'), visiblility, sidebar_settings)}} {{ user_checkbox_row("sidebar_view", "sidebar_random", _('Show random books'), visiblility, sidebar_settings)}}
@ -136,6 +154,9 @@
</tr> </tr>
</thead> </thead>
</table> </table>
<div class="errorlink">
<div class="btn btn-default" data-back="{{ url_for('admin.admin') }}" id="back">{{_('Back')}}</div>
</div>
{% endblock %} {% endblock %}
{% block modal %} {% block modal %}
{{ delete_confirm_modal() }} {{ delete_confirm_modal() }}
@ -146,7 +167,7 @@
<script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table-editable.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table-editable.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-editable.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-editable.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/bootstrap-select.min.js')}}"></script>
<script src="{{ url_for('static', filename='js/table.js') }}"></script> <script src="{{ url_for('static', filename='js/table.js') }}"></script>
<script>
</script>
{% endblock %} {% endblock %}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -44,7 +44,7 @@ from sqlalchemy import String, Integer, SmallInteger, Boolean, DateTime, Float,
from sqlalchemy.orm.attributes import flag_modified from sqlalchemy.orm.attributes import flag_modified
from sqlalchemy.sql.expression import func from sqlalchemy.sql.expression import func
try: try:
# Compability with sqlalchemy 2.0 # Compatibility with sqlalchemy 2.0
from sqlalchemy.orm import declarative_base from sqlalchemy.orm import declarative_base
except ImportError: except ImportError:
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
@ -713,9 +713,12 @@ def init_db(app_db_path):
create_anonymous_user(session) create_anonymous_user(session)
if cli.user_credentials: if cli.user_credentials:
username, password = cli.user_credentials.split(':') username, password = cli.user_credentials.split(':', 1)
user = session.query(User).filter(func.lower(User.name) == username.lower()).first() user = session.query(User).filter(func.lower(User.name) == username.lower()).first()
if user: if user:
if not password:
print("Empty password is not allowed")
sys.exit(4)
user.password = generate_password_hash(password) user.password = generate_password_hash(password)
if session_commit() == "": if session_commit() == "":
print("Password for user '{}' changed".format(username)) print("Password for user '{}' changed".format(username))

View File

@ -103,20 +103,21 @@ class Updater(threading.Thread):
time.sleep(2) time.sleep(2)
return True return True
except requests.exceptions.HTTPError as ex: except requests.exceptions.HTTPError as ex:
log.info(u'HTTP Error %s', ex) log.error(u'HTTP Error %s', ex)
self.status = 8 self.status = 8
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
log.info(u'Connection error') log.error(u'Connection error')
self.status = 9 self.status = 9
except requests.exceptions.Timeout: except requests.exceptions.Timeout:
log.info(u'Timeout while establishing connection') log.error(u'Timeout while establishing connection')
self.status = 10 self.status = 10
except (requests.exceptions.RequestException, zipfile.BadZipFile): except (requests.exceptions.RequestException, zipfile.BadZipFile):
self.status = 11 self.status = 11
log.info(u'General error') log.error(u'General error')
except (IOError, OSError): except (IOError, OSError) as ex:
self.status = 12 self.status = 12
log.info(u'Update File Could Not be Saved in Temp Dir') log.error(u'Possible Reason for error: update file could not be saved in temp dir')
log.debug_or_exception(ex)
self.pause() self.pause()
return False return False
@ -182,39 +183,50 @@ class Updater(threading.Thread):
@classmethod @classmethod
def moveallfiles(cls, root_src_dir, root_dst_dir): def moveallfiles(cls, root_src_dir, root_dst_dir):
change_permissions = True
new_permissions = os.stat(root_dst_dir) new_permissions = os.stat(root_dst_dir)
if sys.platform == "win32" or sys.platform == "darwin": log.debug('Performing Update on OS-System: %s', sys.platform)
change_permissions = False change_permissions = (sys.platform == "win32" or sys.platform == "darwin")
else:
log.debug('Update on OS-System : %s', sys.platform)
for src_dir, __, files in os.walk(root_src_dir): for src_dir, __, files in os.walk(root_src_dir):
dst_dir = src_dir.replace(root_src_dir, root_dst_dir, 1) dst_dir = src_dir.replace(root_src_dir, root_dst_dir, 1)
if not os.path.exists(dst_dir): if not os.path.exists(dst_dir):
os.makedirs(dst_dir) try:
log.debug('Create-Dir: %s', dst_dir) os.makedirs(dst_dir)
log.debug('Create directory: {}', dst_dir)
except OSError as e:
log.error('Failed creating folder: {} with error {}'.format(dst_dir, e))
if change_permissions: if change_permissions:
# print('Permissions: User '+str(new_permissions.st_uid)+' Group '+str(new_permissions.st_uid)) try:
os.chown(dst_dir, new_permissions.st_uid, new_permissions.st_gid) os.chown(dst_dir, new_permissions.st_uid, new_permissions.st_gid)
except OSError as e:
old_permissions = os.stat(dst_dir)
log.error('Failed changing permissions of %s. Before: %s:%s After %s:%s error: %s',
dst_dir, old_permissions.st_uid, old_permissions.st_gid,
new_permissions.st_uid, new_permissions.st_gid, e)
for file_ in files: for file_ in files:
src_file = os.path.join(src_dir, file_) src_file = os.path.join(src_dir, file_)
dst_file = os.path.join(dst_dir, file_) dst_file = os.path.join(dst_dir, file_)
if os.path.exists(dst_file): if os.path.exists(dst_file):
if change_permissions: if change_permissions:
permission = os.stat(dst_file) permission = os.stat(dst_file)
log.debug('Remove file before copy: %s', dst_file) try:
os.remove(dst_file) os.remove(dst_file)
log.debug('Remove file before copy: %s', dst_file)
except OSError as e:
log.error('Failed removing file: {} with error {}'.format(dst_file, e))
else: else:
if change_permissions: if change_permissions:
permission = new_permissions permission = new_permissions
shutil.move(src_file, dst_dir) try:
log.debug('Move File %s to %s', src_file, dst_dir) shutil.move(src_file, dst_dir)
log.debug('Move File %s to %s', src_file, dst_dir)
except OSError as ex:
log.error('Failed moving file from {} to {} with error {}'.format(src_file, dst_dir, ex))
if change_permissions: if change_permissions:
try: try:
os.chown(dst_file, permission.st_uid, permission.st_gid) os.chown(dst_file, permission.st_uid, permission.st_gid)
except OSError as e: except OSError as e:
old_permissions = os.stat(dst_file) old_permissions = os.stat(dst_file)
log.debug('Fail change permissions of %s. Before: %s:%s After %s:%s error: %s', log.error('Failed changing permissions of %s. Before: %s:%s After %s:%s error: %s',
dst_file, old_permissions.st_uid, old_permissions.st_gid, dst_file, old_permissions.st_uid, old_permissions.st_gid,
permission.st_uid, permission.st_gid, e) permission.st_uid, permission.st_gid, e)
return return
@ -266,9 +278,8 @@ class Updater(threading.Thread):
shutil.rmtree(item_path, ignore_errors=True) shutil.rmtree(item_path, ignore_errors=True)
else: else:
try: try:
log.debug("Delete file %s", item_path)
# log_from_thread("Delete file " + item_path)
os.remove(item_path) os.remove(item_path)
log.debug("Delete file %s", item_path)
except OSError: except OSError:
log.debug("Could not remove: %s", item_path) log.debug("Could not remove: %s", item_path)
shutil.rmtree(source, ignore_errors=True) shutil.rmtree(source, ignore_errors=True)
@ -283,11 +294,13 @@ class Updater(threading.Thread):
@classmethod @classmethod
def _nightly_version_info(cls): def _nightly_version_info(cls):
if is_sha1(constants.NIGHTLY_VERSION[0]) and len(constants.NIGHTLY_VERSION[1]) > 0: if is_sha1(constants.NIGHTLY_VERSION[0]) and len(constants.NIGHTLY_VERSION[1]) > 0:
log.debug("Nightly version: {}, {}".format(constants.NIGHTLY_VERSION[0], constants.NIGHTLY_VERSION[1]))
return {'version': constants.NIGHTLY_VERSION[0], 'datetime': constants.NIGHTLY_VERSION[1]} return {'version': constants.NIGHTLY_VERSION[0], 'datetime': constants.NIGHTLY_VERSION[1]}
return False return False
@classmethod @classmethod
def _stable_version_info(cls): def _stable_version_info(cls):
log.debug("Stable version: {}".format(constants.STABLE_VERSION))
return constants.STABLE_VERSION # Current version return constants.STABLE_VERSION # Current version
@staticmethod @staticmethod
@ -381,6 +394,7 @@ class Updater(threading.Thread):
# if 'committer' in update_data and 'message' in update_data: # if 'committer' in update_data and 'message' in update_data:
try: try:
log.debug("A new update is available.")
status['success'] = True status['success'] = True
status['message'] = _( status['message'] = _(
u'A new update is available. Click on the button below to update to the latest version.') u'A new update is available. Click on the button below to update to the latest version.')
@ -401,6 +415,7 @@ class Updater(threading.Thread):
except (IndexError, KeyError): except (IndexError, KeyError):
status['success'] = False status['success'] = False
status['message'] = _(u'Could not fetch update information') status['message'] = _(u'Could not fetch update information')
log.error("Could not fetch update information")
return json.dumps(status) return json.dumps(status)
return '' return ''
@ -468,6 +483,7 @@ class Updater(threading.Thread):
# we are already on newest version, no update available # we are already on newest version, no update available
if 'tag_name' not in commit[0]: if 'tag_name' not in commit[0]:
status['message'] = _(u'Unexpected data while reading update information') status['message'] = _(u'Unexpected data while reading update information')
log.error("Unexpected data while reading update information")
return json.dumps(status) return json.dumps(status)
if commit[0]['tag_name'] == version: if commit[0]['tag_name'] == version:
status.update({ status.update({

View File

@ -75,8 +75,9 @@ def load_user_from_auth_header(header_val):
basic_username = basic_password = '' # nosec basic_username = basic_password = '' # nosec
try: try:
header_val = base64.b64decode(header_val).decode('utf-8') header_val = base64.b64decode(header_val).decode('utf-8')
basic_username = header_val.split(':')[0] # Users with colon are invalid: rfc7617 page 4
basic_password = header_val.split(':')[1] basic_username = header_val.split(':', 1)[0]
basic_password = header_val.split(':', 1)[1]
except (TypeError, UnicodeDecodeError, binascii.Error): except (TypeError, UnicodeDecodeError, binascii.Error):
pass pass
user = _fetch_user_by_name(basic_username) user = _fetch_user_by_name(basic_username)

View File

@ -26,6 +26,7 @@ from datetime import datetime
import json import json
import mimetypes import mimetypes
import chardet # dependency of requests import chardet # dependency of requests
import copy
from babel.dates import format_date from babel.dates import format_date
from babel import Locale as LC from babel import Locale as LC
@ -183,11 +184,12 @@ def toggle_read(book_id):
calibre_db.session.add(new_cc) calibre_db.session.add(new_cc)
calibre_db.session.commit() calibre_db.session.commit()
except (KeyError, AttributeError): except (KeyError, AttributeError):
log.error(u"Custom Column No.%d is not exisiting in calibre database", config.config_read_column) log.error(u"Custom Column No.%d is not existing in calibre database", config.config_read_column)
return "Custom Column No.{} is not existing in calibre database".format(config.config_read_column), 400
except (OperationalError, InvalidRequestError) as e: except (OperationalError, InvalidRequestError) as e:
calibre_db.session.rollback() calibre_db.session.rollback()
log.error(u"Read status could not set: %e", e) log.error(u"Read status could not set: %e", e)
return "Read status could not set: {}".format(e), 400
return "" return ""
@web.route("/ajax/togglearchived/<int:book_id>", methods=['POST']) @web.route("/ajax/togglearchived/<int:book_id>", methods=['POST'])
@ -753,20 +755,50 @@ def books_table():
@web.route("/ajax/listbooks") @web.route("/ajax/listbooks")
@login_required @login_required
def list_books(): def list_books():
off = request.args.get("offset") or 0 off = int(request.args.get("offset") or 0)
limit = request.args.get("limit") or config.config_books_per_page limit = int(request.args.get("limit") or config.config_books_per_page)
# sort = request.args.get("sort")
if request.args.get("order") == 'desc':
order = [db.Books.timestamp.desc()]
else:
order = [db.Books.timestamp.asc()]
search = request.args.get("search") search = request.args.get("search")
total_count = calibre_db.session.query(db.Books).count() sort = request.args.get("sort", "id")
if search: order = request.args.get("order", "").lower()
entries, filtered_count, __ = calibre_db.get_search_results(search, off, order, limit) state = None
join = tuple()
if sort == "state":
state = json.loads(request.args.get("state", "[]"))
elif sort == "tags":
order = [db.Tags.name.asc()] if order == "asc" else [db.Tags.name.desc()]
join = db.books_tags_link,db.Books.id == db.books_tags_link.c.book, db.Tags
elif sort == "series":
order = [db.Series.name.asc()] if order == "asc" else [db.Series.name.desc()]
join = db.books_series_link,db.Books.id == db.books_series_link.c.book, db.Series
elif sort == "publishers":
order = [db.Publishers.name.asc()] if order == "asc" else [db.Publishers.name.desc()]
join = db.books_publishers_link,db.Books.id == db.books_publishers_link.c.book, db.Publishers
elif sort == "authors":
order = [db.Authors.name.asc()] if order == "asc" else [db.Authors.name.desc()]
join = db.books_authors_link,db.Books.id == db.books_authors_link.c.book, db.Authors
elif sort == "languages":
order = [db.Languages.lang_code.asc()] if order == "asc" else [db.Languages.lang_code.desc()]
join = db.books_languages_link,db.Books.id == db.books_languages_link.c.book, db.Languages
elif order and sort in ["sort", "title", "authors_sort", "series_index"]:
order = [text(sort + " " + order)]
elif not state:
order = [db.Books.timestamp.desc()]
total_count = filtered_count = calibre_db.session.query(db.Books).count()
if state:
if search:
books = calibre_db.search_query(search).all()
filtered_count = len(books)
else:
books = calibre_db.session.query(db.Books).filter(calibre_db.common_filters()).all()
entries = calibre_db.get_checkbox_sorted(books, state, off, limit,order)
elif search:
entries, filtered_count, __ = calibre_db.get_search_results(search, off, order, limit, *join)
else: else:
entries, __, __ = calibre_db.fill_indexpage((int(off) / (int(limit)) + 1), limit, db.Books, True, order) entries, __, __ = calibre_db.fill_indexpage((int(off) / (int(limit)) + 1), limit, db.Books, True, order, *join)
filtered_count = total_count
for entry in entries: for entry in entries:
for index in range(0, len(entry.languages)): for index in range(0, len(entry.languages)):
try: try:
@ -816,9 +848,12 @@ def author_list():
charlist = calibre_db.session.query(func.upper(func.substr(db.Authors.sort, 1, 1)).label('char')) \ charlist = calibre_db.session.query(func.upper(func.substr(db.Authors.sort, 1, 1)).label('char')) \
.join(db.books_authors_link).join(db.Books).filter(calibre_db.common_filters()) \ .join(db.books_authors_link).join(db.Books).filter(calibre_db.common_filters()) \
.group_by(func.upper(func.substr(db.Authors.sort, 1, 1))).all() .group_by(func.upper(func.substr(db.Authors.sort, 1, 1))).all()
for entry in entries: # If not creating a copy, readonly databases can not display authornames with "|" in it as changing the name
# starts a change session
autor_copy = copy.deepcopy(entries)
for entry in autor_copy:
entry.Authors.name = entry.Authors.name.replace('|', ',') entry.Authors.name = entry.Authors.name.replace('|', ',')
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist, return render_title_template('list.html', entries=autor_copy, folder='web.books_list', charlist=charlist,
title=u"Authors", page="authorlist", data='author', order=order_no) title=u"Authors", page="authorlist", data='author', order=order_no)
else: else:
abort(404) abort(404)
@ -1083,12 +1118,19 @@ def adv_search_ratings(q, rating_high, rating_low):
def adv_search_read_status(q, read_status): def adv_search_read_status(q, read_status):
if read_status: if read_status:
if config.config_read_column: if config.config_read_column:
if read_status == "True": try:
q = q.join(db.cc_classes[config.config_read_column], isouter=True) \ if read_status == "True":
.filter(db.cc_classes[config.config_read_column].value == True) q = q.join(db.cc_classes[config.config_read_column], isouter=True) \
else: .filter(db.cc_classes[config.config_read_column].value == True)
q = q.join(db.cc_classes[config.config_read_column], isouter=True) \ else:
.filter(coalesce(db.cc_classes[config.config_read_column].value, False) != True) q = q.join(db.cc_classes[config.config_read_column], isouter=True) \
.filter(coalesce(db.cc_classes[config.config_read_column].value, False) != True)
except (KeyError, AttributeError):
log.error(u"Custom Column No.%d is not existing in calibre database", config.config_read_column)
flash(_("Custom Column No.%(column)d is not existing in calibre database",
column=config.config_read_column),
category="error")
return q
else: else:
if read_status == "True": if read_status == "True":
q = q.join(ub.ReadBook, db.Books.id == ub.ReadBook.book_id, isouter=True) \ q = q.join(ub.ReadBook, db.Books.id == ub.ReadBook.book_id, isouter=True) \
@ -1453,23 +1495,23 @@ def login():
log.info(error) log.info(error)
flash(_(u"Could not login: %(message)s", message=error), category="error") flash(_(u"Could not login: %(message)s", message=error), category="error")
else: else:
ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr) ip_Address = request.headers.get('X-Forwarded-For', request.remote_addr)
log.warning('LDAP Login failed for user "%s" IP-address: %s', form['username'], ipAdress) log.warning('LDAP Login failed for user "%s" IP-address: %s', form['username'], ip_Address)
flash(_(u"Wrong Username or Password"), category="error") flash(_(u"Wrong Username or Password"), category="error")
else: else:
ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr) ip_Address = request.headers.get('X-Forwarded-For', request.remote_addr)
if 'forgot' in form and form['forgot'] == 'forgot': if 'forgot' in form and form['forgot'] == 'forgot':
if user != None and user.name != "Guest": if user != None and user.name != "Guest":
ret, __ = reset_password(user.id) ret, __ = reset_password(user.id)
if ret == 1: if ret == 1:
flash(_(u"New Password was send to your email address"), category="info") flash(_(u"New Password was send to your email address"), category="info")
log.info('Password reset for user "%s" IP-address: %s', form['username'], ipAdress) log.info('Password reset for user "%s" IP-address: %s', form['username'], ip_Address)
else: else:
log.error(u"An unknown error occurred. Please try again later") log.error(u"An unknown error occurred. Please try again later")
flash(_(u"An unknown error occurred. Please try again later."), category="error") flash(_(u"An unknown error occurred. Please try again later."), category="error")
else: else:
flash(_(u"Please enter valid username to reset password"), category="error") flash(_(u"Please enter valid username to reset password"), category="error")
log.warning('Username missing for password reset IP-address: %s', ipAdress) log.warning('Username missing for password reset IP-address: %s', ip_Address)
else: else:
if user and check_password_hash(str(user.password), form['password']) and user.name != "Guest": if user and check_password_hash(str(user.password), form['password']) and user.name != "Guest":
login_user(user, remember=bool(form.get('remember_me'))) login_user(user, remember=bool(form.get('remember_me')))
@ -1478,7 +1520,7 @@ def login():
config.config_is_initial = False config.config_is_initial = False
return redirect_back(url_for("web.index")) return redirect_back(url_for("web.index"))
else: else:
log.warning('Login failed for user "%s" IP-address: %s', form['username'], ipAdress) log.warning('Login failed for user "%s" IP-address: %s', form['username'], ip_Address)
flash(_(u"Wrong Username or Password"), category="error") flash(_(u"Wrong Username or Password"), category="error")
next_url = request.args.get('next', default=url_for("web.index"), type=str) next_url = request.args.get('next', default=url_for("web.index"), type=str)

File diff suppressed because it is too large Load Diff

View File

@ -26,8 +26,8 @@ python-ldap>=3.0.0,<3.4.0
Flask-SimpleLDAP>=1.4.0,<1.5.0 Flask-SimpleLDAP>=1.4.0,<1.5.0
#oauth #oauth
Flask-Dance>=1.4.0,<3.1.0 Flask-Dance>=1.4.0,<4.1.0
SQLAlchemy-Utils>=0.33.5,<0.37.0 SQLAlchemy-Utils>=0.33.5,<0.38.0
# extracting metadata # extracting metadata
lxml>=3.8.0,<4.7.0 lxml>=3.8.0,<4.7.0

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,4 @@
output_list = Array(); output_list = Array();
/* Level - 0: Summary; 1: Failed; 2: All; 3: Skipped 4: Error*/ /* Level - 0: Summary; 1: Failed; 2: All; 3: Skipped 4: Error*/
@ -24,9 +25,9 @@ function showCase(level) {
row.classList.add('hiddenRow'); row.classList.add('hiddenRow');
} }
} }
// Show skipped if all or skipped or summary problems selected // Show skipped if all or skipped selected
if (id.substr(0,2) == 'st') { if (id.substr(0,2) == 'st') {
if (level ==2 || level ==3 || level == 5) { if (level ==2 || level ==3) {
row.classList.remove('hiddenRow'); row.classList.remove('hiddenRow');
} }
else { else {