Commit a47d6cd9 authored by Ozzie Isaacs's avatar Ozzie Isaacs

Fix confirm dialog database change

Gdrive setup basically working again
Moved basicconfig behind login
Database setup separated from other setup
Config page is using ajax (flask >2 and slow computers)
parent dcdb5e2a
...@@ -83,7 +83,9 @@ log = logger.create() ...@@ -83,7 +83,9 @@ log = logger.create()
from . import services from . import services
db.CalibreDB.setup_db(config, cli.settingspath) db.CalibreDB.update_config(config)
db.CalibreDB.setup_db(config.config_calibre_dir, cli.settingspath)
calibre_db = db.CalibreDB() calibre_db = db.CalibreDB()
......
...@@ -40,7 +40,7 @@ from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError ...@@ -40,7 +40,7 @@ from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError
from sqlalchemy.sql.expression import func, or_, text 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
from . import db, calibre_db, ub, web_server, get_locale, config, updater_thread, babel, gdriveutils from . import db, calibre_db, ub, web_server, get_locale, config, updater_thread, babel, gdriveutils
from .helper import check_valid_domain, send_test_mail, reset_password, generate_password_hash, check_email, \ from .helper import check_valid_domain, send_test_mail, reset_password, generate_password_hash, check_email, \
valid_email, check_username valid_email, check_username
...@@ -97,19 +97,6 @@ def admin_required(f): ...@@ -97,19 +97,6 @@ def admin_required(f):
return inner return inner
def unconfigured(f):
"""
Checks if calibre-web instance is not configured
"""
@wraps(f)
def inner(*args, **kwargs):
if not config.db_configured:
return f(*args, **kwargs)
abort(403)
return inner
@admi.before_app_request @admi.before_app_request
def before_request(): def before_request():
if current_user.is_authenticated: if current_user.is_authenticated:
...@@ -124,10 +111,14 @@ def before_request(): ...@@ -124,10 +111,14 @@ def before_request():
g.shelves_access = ub.session.query(ub.Shelf).filter( g.shelves_access = ub.session.query(ub.Shelf).filter(
or_(ub.Shelf.is_public == 1, ub.Shelf.user_id == current_user.id)).order_by(ub.Shelf.name).all() or_(ub.Shelf.is_public == 1, ub.Shelf.user_id == current_user.id)).order_by(ub.Shelf.name).all()
if '/static/' not in request.path and not config.db_configured and \ if '/static/' not in request.path and not config.db_configured and \
request.endpoint not in ('admin.basic_configuration', request.endpoint not in ('admin.ajax_db_config',
'login', 'admin.simulatedbchange',
'admin.config_pathchooser'): 'admin.db_configuration',
return redirect(url_for('admin.basic_configuration')) 'web.login',
'web.logout',
'admin.load_dialogtexts',
'admin.ajax_pathchooser'):
return redirect(url_for('admin.db_configuration'))
@admi.route("/admin") @admi.route("/admin")
...@@ -194,16 +185,46 @@ def admin(): ...@@ -194,16 +185,46 @@ def admin():
feature_support=feature_support, kobo_support=kobo_support, feature_support=feature_support, kobo_support=kobo_support,
title=_(u"Admin page"), page="admin") title=_(u"Admin page"), page="admin")
@admi.route("/admin/dbconfig", methods=["GET", "POST"])
@login_required
@admin_required
def db_configuration():
if request.method == "POST":
return _db_configuration_update_helper()
return _db_configuration_result()
@admi.route("/admin/config", methods=["GET", "POST"])
@admi.route("/admin/config", methods=["GET"])
@login_required @login_required
@admin_required @admin_required
def configuration(): def configuration():
if request.method == "POST": return render_title_template("config_edit.html",
return _configuration_update_helper(True) config=config,
return _configuration_result() provider=oauthblueprints,
feature_support=feature_support,
title=_(u"Basic Configuration"), page="config")
@admi.route("/admin/ajaxconfig", methods=["POST"])
@login_required
@admin_required
def ajax_config():
return _configuration_update_helper()
@admi.route("/admin/ajaxdbconfig", methods=["POST"])
@login_required
@admin_required
def ajax_db_config():
return _db_configuration_update_helper()
@admi.route("/admin/alive", methods=["GET"])
@login_required
@admin_required
def calibreweb_alive():
return "", 200
@admi.route("/admin/viewconfig") @admi.route("/admin/viewconfig")
@login_required @login_required
@admin_required @admin_required
...@@ -539,10 +560,10 @@ def update_view_configuration(): ...@@ -539,10 +560,10 @@ def update_view_configuration():
return view_configuration() return view_configuration()
@admi.route("/ajax/loaddialogtexts/<element_id>") @admi.route("/ajax/loaddialogtexts/<element_id>", methods=['POST'])
@login_required @login_required
def load_dialogtexts(element_id): def load_dialogtexts(element_id):
texts = {"header": "", "main": ""} texts = {"header": "", "main": "", "valid": 1}
if element_id == "config_delete_kobo_token": if element_id == "config_delete_kobo_token":
texts["main"] = _('Do you really want to delete the Kobo Token?') texts["main"] = _('Do you really want to delete the Kobo Token?')
elif element_id == "btndeletedomain": elif element_id == "btndeletedomain":
...@@ -563,6 +584,8 @@ def load_dialogtexts(element_id): ...@@ -563,6 +584,8 @@ def load_dialogtexts(element_id):
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)?')
elif element_id == "kobo_only_shelves_sync": elif element_id == "kobo_only_shelves_sync":
texts["main"] = _('Are you sure you want to change shelf sync behavior for the selected user(s)?') texts["main"] = _('Are you sure you want to change shelf sync behavior for the selected user(s)?')
elif element_id == "db_submit":
texts["main"] = _('Are you sure you want to change Calibre libray location?')
return json.dumps(texts) return json.dumps(texts)
...@@ -867,14 +890,6 @@ def list_restriction(res_type, user_id): ...@@ -867,14 +890,6 @@ def list_restriction(res_type, user_id):
return response return response
@admi.route("/basicconfig/pathchooser/")
@unconfigured
def config_pathchooser():
if filepicker:
return pathchooser()
abort(403)
@admi.route("/ajax/pathchooser/") @admi.route("/ajax/pathchooser/")
@login_required @login_required
@admin_required @admin_required
...@@ -963,16 +978,6 @@ def pathchooser(): ...@@ -963,16 +978,6 @@ def pathchooser():
return json.dumps(context) return json.dumps(context)
@admi.route("/basicconfig", methods=["GET", "POST"])
@unconfigured
def basic_configuration():
logout_user()
if request.method == "POST":
log.debug("Basic Configuration send")
return _configuration_update_helper(configured=filepicker)
return _configuration_result(configured=filepicker)
def _config_int(to_save, x, func=int): def _config_int(to_save, x, func=int):
return config.set_from_dictionary(to_save, x, func) return config.set_from_dictionary(to_save, x, func)
...@@ -991,23 +996,24 @@ def _config_string(to_save, x): ...@@ -991,23 +996,24 @@ def _config_string(to_save, x):
def _configuration_gdrive_helper(to_save): def _configuration_gdrive_helper(to_save):
gdrive_error = None gdrive_error = None
gdrive_secrets = {} if to_save.get("config_use_google_drive"):
gdrive_secrets = {}
if not os.path.isfile(gdriveutils.SETTINGS_YAML):
config.config_use_google_drive = False if not os.path.isfile(gdriveutils.SETTINGS_YAML):
config.config_use_google_drive = False
if gdrive_support:
gdrive_error = gdriveutils.get_error_text(gdrive_secrets) if gdrive_support:
if "config_use_google_drive" in to_save and not config.config_use_google_drive and not gdrive_error: gdrive_error = gdriveutils.get_error_text(gdrive_secrets)
with open(gdriveutils.CLIENT_SECRETS, 'r') as settings: if "config_use_google_drive" in to_save and not config.config_use_google_drive and not gdrive_error:
gdrive_secrets = json.load(settings)['web'] with open(gdriveutils.CLIENT_SECRETS, 'r') as settings:
if not gdrive_secrets: gdrive_secrets = json.load(settings)['web']
return _configuration_result(_('client_secrets.json Is Not Configured For Web Application')) if not gdrive_secrets:
gdriveutils.update_settings( return _configuration_result(_('client_secrets.json Is Not Configured For Web Application'))
gdrive_secrets['client_id'], gdriveutils.update_settings(
gdrive_secrets['client_secret'], gdrive_secrets['client_id'],
gdrive_secrets['redirect_uris'][0] gdrive_secrets['client_secret'],
) gdrive_secrets['redirect_uris'][0]
)
# always show google drive settings, but in case of error deny support # always show google drive settings, but in case of error deny support
new_gdrive_value = (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)
...@@ -1041,23 +1047,23 @@ def _configuration_oauth_helper(to_save): ...@@ -1041,23 +1047,23 @@ def _configuration_oauth_helper(to_save):
return reboot_required return reboot_required
def _configuration_logfile_helper(to_save, gdrive_error): def _configuration_logfile_helper(to_save):
reboot_required = False reboot_required = False
reboot_required |= _config_int(to_save, "config_log_level") reboot_required |= _config_int(to_save, "config_log_level")
reboot_required |= _config_string(to_save, "config_logfile") reboot_required |= _config_string(to_save, "config_logfile")
if not logger.is_valid_logfile(config.config_logfile): if not logger.is_valid_logfile(config.config_logfile):
return reboot_required, \ return reboot_required, \
_configuration_result(_('Logfile Location is not Valid, Please Enter Correct Path'), gdrive_error) _configuration_result(_('Logfile Location is not Valid, Please Enter Correct Path'))
reboot_required |= _config_checkbox_int(to_save, "config_access_log") reboot_required |= _config_checkbox_int(to_save, "config_access_log")
reboot_required |= _config_string(to_save, "config_access_logfile") reboot_required |= _config_string(to_save, "config_access_logfile")
if not logger.is_valid_logfile(config.config_access_logfile): if not logger.is_valid_logfile(config.config_access_logfile):
return reboot_required, \ return reboot_required, \
_configuration_result(_('Access Logfile Location is not Valid, Please Enter Correct Path'), gdrive_error) _configuration_result(_('Access Logfile Location is not Valid, Please Enter Correct Path'))
return reboot_required, None return reboot_required, None
def _configuration_ldap_helper(to_save, gdrive_error): def _configuration_ldap_helper(to_save):
reboot_required = False reboot_required = False
reboot_required |= _config_string(to_save, "config_ldap_provider_url") reboot_required |= _config_string(to_save, "config_ldap_provider_url")
reboot_required |= _config_int(to_save, "config_ldap_port") reboot_required |= _config_int(to_save, "config_ldap_port")
...@@ -1084,44 +1090,37 @@ def _configuration_ldap_helper(to_save, gdrive_error): ...@@ -1084,44 +1090,37 @@ def _configuration_ldap_helper(to_save, gdrive_error):
or not config.config_ldap_dn \ or not config.config_ldap_dn \
or not config.config_ldap_user_object: or not config.config_ldap_user_object:
return reboot_required, _configuration_result(_('Please Enter a LDAP Provider, ' return reboot_required, _configuration_result(_('Please Enter a LDAP Provider, '
'Port, DN and User Object Identifier'), gdrive_error) 'Port, DN and User Object Identifier'))
if config.config_ldap_authentication > constants.LDAP_AUTH_ANONYMOUS: if config.config_ldap_authentication > constants.LDAP_AUTH_ANONYMOUS:
if config.config_ldap_authentication > constants.LDAP_AUTH_UNAUTHENTICATE: if config.config_ldap_authentication > constants.LDAP_AUTH_UNAUTHENTICATE:
if not config.config_ldap_serv_username or not bool(config.config_ldap_serv_password): if not config.config_ldap_serv_username or not bool(config.config_ldap_serv_password):
return reboot_required, _configuration_result('Please Enter a LDAP Service Account and Password', return reboot_required, _configuration_result(_('Please Enter a LDAP Service Account and Password'))
gdrive_error)
else: else:
if not config.config_ldap_serv_username: if not config.config_ldap_serv_username:
return reboot_required, _configuration_result('Please Enter a LDAP Service Account', gdrive_error) return reboot_required, _configuration_result(_('Please Enter a LDAP Service Account'))
if config.config_ldap_group_object_filter: if config.config_ldap_group_object_filter:
if config.config_ldap_group_object_filter.count("%s") != 1: if config.config_ldap_group_object_filter.count("%s") != 1:
return reboot_required, \ return reboot_required, \
_configuration_result(_('LDAP Group Object Filter Needs to Have One "%s" Format Identifier'), _configuration_result(_('LDAP Group Object Filter Needs to Have One "%s" Format Identifier'))
gdrive_error)
if config.config_ldap_group_object_filter.count("(") != config.config_ldap_group_object_filter.count(")"): if config.config_ldap_group_object_filter.count("(") != config.config_ldap_group_object_filter.count(")"):
return reboot_required, _configuration_result(_('LDAP Group Object Filter Has Unmatched Parenthesis'), return reboot_required, _configuration_result(_('LDAP Group Object Filter Has Unmatched Parenthesis'))
gdrive_error)
if config.config_ldap_user_object.count("%s") != 1: if config.config_ldap_user_object.count("%s") != 1:
return reboot_required, \ return reboot_required, \
_configuration_result(_('LDAP User Object Filter needs to Have One "%s" Format Identifier'), _configuration_result(_('LDAP User Object Filter needs to Have One "%s" Format Identifier'))
gdrive_error)
if config.config_ldap_user_object.count("(") != config.config_ldap_user_object.count(")"): if config.config_ldap_user_object.count("(") != config.config_ldap_user_object.count(")"):
return reboot_required, _configuration_result(_('LDAP User Object Filter Has Unmatched Parenthesis'), return reboot_required, _configuration_result(_('LDAP User Object Filter Has Unmatched Parenthesis'))
gdrive_error)
if to_save.get("ldap_import_user_filter") == '0': if to_save.get("ldap_import_user_filter") == '0':
config.config_ldap_member_user_object = "" config.config_ldap_member_user_object = ""
else: else:
if config.config_ldap_member_user_object.count("%s") != 1: if config.config_ldap_member_user_object.count("%s") != 1:
return reboot_required, \ return reboot_required, \
_configuration_result(_('LDAP Member User Filter needs to Have One "%s" Format Identifier'), _configuration_result(_('LDAP Member User Filter needs to Have One "%s" Format Identifier'))
gdrive_error)
if config.config_ldap_member_user_object.count("(") != config.config_ldap_member_user_object.count(")"): if config.config_ldap_member_user_object.count("(") != config.config_ldap_member_user_object.count(")"):
return reboot_required, _configuration_result(_('LDAP Member User Filter Has Unmatched Parenthesis'), return reboot_required, _configuration_result(_('LDAP Member User Filter Has Unmatched Parenthesis'))
gdrive_error)
if config.config_ldap_cacert_path or config.config_ldap_cert_path or config.config_ldap_key_path: if config.config_ldap_cacert_path or config.config_ldap_cert_path or config.config_ldap_key_path:
if not (os.path.isfile(config.config_ldap_cacert_path) and if not (os.path.isfile(config.config_ldap_cacert_path) and
...@@ -1129,13 +1128,31 @@ def _configuration_ldap_helper(to_save, gdrive_error): ...@@ -1129,13 +1128,31 @@ def _configuration_ldap_helper(to_save, gdrive_error):
os.path.isfile(config.config_ldap_key_path)): os.path.isfile(config.config_ldap_key_path)):
return reboot_required, \ return reboot_required, \
_configuration_result(_('LDAP CACertificate, Certificate or Key Location is not Valid, ' _configuration_result(_('LDAP CACertificate, Certificate or Key Location is not Valid, '
'Please Enter Correct Path'), 'Please Enter Correct Path'))
gdrive_error)
return reboot_required, None return reboot_required, None
def _configuration_update_helper(configured): @admi.route("/ajax/simulatedbchange", methods=['POST'])
reboot_required = False @login_required
@admin_required
def simulatedbchange():
db_change, db_valid = _db_simulate_change()
return Response(json.dumps({"change": db_change, "valid": db_valid}), mimetype='application/json')
def _db_simulate_change():
param = request.form.to_dict()
to_save = {}
to_save['config_calibre_dir'] = re.sub(r'[\\/]metadata\.db$',
'',
param['config_calibre_dir'],
flags=re.IGNORECASE).strip()
db_change = config.config_calibre_dir != to_save["config_calibre_dir"] and config.config_calibre_dir
db_valid = calibre_db.check_valid_db(to_save["config_calibre_dir"], ub.app_DB_path)
return db_change, db_valid
def _db_configuration_update_helper():
db_change = False db_change = False
to_save = request.form.to_dict() to_save = request.form.to_dict()
gdrive_error = None gdrive_error = None
...@@ -1145,24 +1162,47 @@ def _configuration_update_helper(configured): ...@@ -1145,24 +1162,47 @@ def _configuration_update_helper(configured):
to_save['config_calibre_dir'], to_save['config_calibre_dir'],
flags=re.IGNORECASE) flags=re.IGNORECASE)
try: try:
db_change |= _config_string(to_save, "config_calibre_dir") db_change, db_valid = _db_simulate_change()
# gdrive_error drive setup # gdrive_error drive setup
gdrive_error = _configuration_gdrive_helper(to_save) gdrive_error = _configuration_gdrive_helper(to_save)
except (OperationalError, InvalidRequestError):
ub.session.rollback()
log.error("Settings DB is not Writeable")
_db_configuration_result(_("Settings DB is not Writeable"), gdrive_error)
try:
metadata_db = os.path.join(to_save['config_calibre_dir'], "metadata.db")
if config.config_use_google_drive and is_gdrive_ready() and not os.path.exists(metadata_db):
gdriveutils.downloadFile(None, "metadata.db", metadata_db)
db_change = True
except Exception as ex:
return _db_configuration_result('{}'.format(ex), gdrive_error)
if db_change or not db_valid or not config.db_configured:
if not calibre_db.setup_db(to_save['config_calibre_dir'], ub.app_DB_path):
return _db_configuration_result(_('DB Location is not Valid, Please Enter Correct Path'),
gdrive_error)
_config_string(to_save, "config_calibre_dir")
calibre_db.update_config(config)
if not os.access(os.path.join(config.config_calibre_dir, "metadata.db"), os.W_OK):
flash(_(u"DB is not Writeable"), category="warning")
# warning = {'type': "warning", 'message': _(u"DB is not Writeable")}
config.save()
return _db_configuration_result(None, gdrive_error)
def _configuration_update_helper():
reboot_required = False
to_save = request.form.to_dict()
try:
reboot_required |= _config_int(to_save, "config_port") reboot_required |= _config_int(to_save, "config_port")
reboot_required |= _config_string(to_save, "config_keyfile") reboot_required |= _config_string(to_save, "config_keyfile")
if config.config_keyfile and not os.path.isfile(config.config_keyfile): if config.config_keyfile and not os.path.isfile(config.config_keyfile):
return _configuration_result(_('Keyfile Location is not Valid, Please Enter Correct Path'), return _configuration_result(_('Keyfile Location is not Valid, Please Enter Correct Path'))
gdrive_error,
configured)
reboot_required |= _config_string(to_save, "config_certfile") reboot_required |= _config_string(to_save, "config_certfile")
if config.config_certfile and not os.path.isfile(config.config_certfile): if config.config_certfile and not os.path.isfile(config.config_certfile):
return _configuration_result(_('Certfile Location is not Valid, Please Enter Correct Path'), return _configuration_result(_('Certfile Location is not Valid, Please Enter Correct Path'))
gdrive_error,
configured)
_config_checkbox_int(to_save, "config_uploading") _config_checkbox_int(to_save, "config_uploading")
# Reboot on config_anonbrowse with enabled ldap, as decoraters are changed in this case # Reboot on config_anonbrowse with enabled ldap, as decoraters are changed in this case
...@@ -1186,15 +1226,14 @@ def _configuration_update_helper(configured): ...@@ -1186,15 +1226,14 @@ def _configuration_update_helper(configured):
reboot_required |= _config_int(to_save, "config_login_type") reboot_required |= _config_int(to_save, "config_login_type")
# LDAP configurator, # LDAP configurator
if config.config_login_type == constants.LOGIN_LDAP: if config.config_login_type == constants.LOGIN_LDAP:
reboot, message = _configuration_ldap_helper(to_save, gdrive_error) reboot, message = _configuration_ldap_helper(to_save)
if message: if message:
return message return message
reboot_required |= reboot reboot_required |= reboot
# Remote login configuration # Remote login configuration
_config_checkbox(to_save, "config_remote_login") _config_checkbox(to_save, "config_remote_login")
if not config.config_remote_login: if not config.config_remote_login:
ub.session.query(ub.RemoteAuthToken).filter(ub.RemoteAuthToken.token_type == 0).delete() ub.session.query(ub.RemoteAuthToken).filter(ub.RemoteAuthToken.token_type == 0).delete()
...@@ -1218,7 +1257,7 @@ def _configuration_update_helper(configured): ...@@ -1218,7 +1257,7 @@ def _configuration_update_helper(configured):
if config.config_login_type == constants.LOGIN_OAUTH: if config.config_login_type == constants.LOGIN_OAUTH:
reboot_required |= _configuration_oauth_helper(to_save) reboot_required |= _configuration_oauth_helper(to_save)
reboot, message = _configuration_logfile_helper(to_save, gdrive_error) reboot, message = _configuration_logfile_helper(to_save)
if message: if message:
return message return message
reboot_required |= reboot reboot_required |= reboot
...@@ -1227,67 +1266,55 @@ def _configuration_update_helper(configured): ...@@ -1227,67 +1266,55 @@ def _configuration_update_helper(configured):
if "config_rarfile_location" in to_save: if "config_rarfile_location" in to_save:
unrar_status = helper.check_unrar(config.config_rarfile_location) unrar_status = helper.check_unrar(config.config_rarfile_location)
if unrar_status: if unrar_status:
return _configuration_result(unrar_status, gdrive_error, configured) return _configuration_result(unrar_status)
except (OperationalError, InvalidRequestError): except (OperationalError, InvalidRequestError):
ub.session.rollback() ub.session.rollback()
log.error("Settings DB is not Writeable") log.error("Settings DB is not Writeable")
_configuration_result(_("Settings DB is not Writeable"), gdrive_error, configured) _configuration_result(_("Settings DB is not Writeable"))
try:
metadata_db = os.path.join(config.config_calibre_dir, "metadata.db")
if config.config_use_google_drive and is_gdrive_ready() and not os.path.exists(metadata_db):
gdriveutils.downloadFile(None, "metadata.db", metadata_db)
db_change = True
except Exception as ex:
return _configuration_result('%s' % ex, gdrive_error, configured)
if db_change:
if not calibre_db.setup_db(config, ub.app_DB_path):
return _configuration_result(_('DB Location is not Valid, Please Enter Correct Path'),
gdrive_error,
configured)
if not os.access(os.path.join(config.config_calibre_dir, "metadata.db"), os.W_OK):
flash(_(u"DB is not Writeable"), category="warning")
config.save() config.save()
flash(_(u"Calibre-Web configuration updated"), category="success")
if reboot_required: if reboot_required:
web_server.stop(True) web_server.stop(True)
return _configuration_result(None, gdrive_error, configured) return _configuration_result(None, reboot_required)
def _configuration_result(error_flash=None, reboot=False):
resp = {}
if error_flash:
log.error(error_flash)
config.load()
resp['result'] = [{'type': "danger", 'message': error_flash}]
else:
resp['result'] = [{'type': "success", 'message':_(u"Calibre-Web configuration updated")}]
resp['reboot'] = reboot
resp['config_upload']= config.config_upload_formats
return Response(json.dumps(resp), mimetype='application/json')
def _configuration_result(error_flash=None, gdrive_error=None, configured=True): def _db_configuration_result(error_flash=None, gdrive_error=None):
gdrive_authenticate = not is_gdrive_ready() gdrive_authenticate = not is_gdrive_ready()
gdrivefolders = [] gdrivefolders = []
if gdrive_error is None: if not gdrive_error and config.config_use_google_drive:
gdrive_error = gdriveutils.get_error_text() gdrive_error = gdriveutils.get_error_text()
if gdrive_error and gdrive_support: if gdrive_error and gdrive_support:
log.error(gdrive_error) log.error(gdrive_error)
gdrive_error = _(gdrive_error) gdrive_error = _(gdrive_error)
flash(gdrive_error, category="error")
else: else:
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_login_button = config.db_configured and not current_user.is_authenticated
if error_flash: if error_flash:
log.error(error_flash) log.error(error_flash)
config.load() config.load()
flash(error_flash, category="error") flash(error_flash, category="error")
show_login_button = False
return render_title_template("config_edit.html", return render_title_template("config_db.html",
config=config, config=config,
provider=oauthblueprints,
show_back_button=show_back_button,
show_login_button=show_login_button,
show_authenticate_google_drive=gdrive_authenticate, show_authenticate_google_drive=gdrive_authenticate,
filepicker=configured,
gdriveError=gdrive_error, gdriveError=gdrive_error,
gdrivefolders=gdrivefolders, gdrivefolders=gdrivefolders,
feature_support=feature_support, feature_support=feature_support,
title=_(u"Basic Configuration"), page="config") title=_(u"Database Configuration"), page="dbconfig")
def _handle_new_user(to_save, content, languages, translations, kobo_support): def _handle_new_user(to_save, content, languages, translations, kobo_support):
......
...@@ -45,7 +45,6 @@ parser.add_argument('-v', '--version', action='version', help='Shows version num ...@@ -45,7 +45,6 @@ parser.add_argument('-v', '--version', action='version', help='Shows version num
version=version_info()) version=version_info())
parser.add_argument('-i', metavar='ip-address', help='Server IP-Address to listen') parser.add_argument('-i', metavar='ip-address', help='Server IP-Address to listen')
parser.add_argument('-s', metavar='user:pass', help='Sets specific username to new password') parser.add_argument('-s', metavar='user:pass', help='Sets specific username to new password')
parser.add_argument('-f', action='store_true', help='Enables filepicker in unconfigured mode')
args = parser.parse_args() args = parser.parse_args()
if sys.version_info < (3, 0): if sys.version_info < (3, 0):
...@@ -114,6 +113,3 @@ user_credentials = args.s or None ...@@ -114,6 +113,3 @@ 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 enabling of filepicker
filepicker = args.f or None
...@@ -347,7 +347,7 @@ class _ConfigSQL(object): ...@@ -347,7 +347,7 @@ class _ConfigSQL(object):
log.error(error) log.error(error)
log.warning("invalidating configuration") log.warning("invalidating configuration")
self.db_configured = False self.db_configured = False
self.config_calibre_dir = None # self.config_calibre_dir = None
self.save() self.save()
......
...@@ -524,19 +524,44 @@ class CalibreDB(): ...@@ -524,19 +524,44 @@ class CalibreDB():
return cc_classes return cc_classes
@classmethod @classmethod
def setup_db(cls, config, app_db_path): def check_valid_db(cls, config_calibre_dir, app_db_path):
if not config_calibre_dir:
return False
dbpath = os.path.join(config_calibre_dir, "metadata.db")
if not os.path.exists(dbpath):
return False
try:
check_engine = create_engine('sqlite://',
echo=False,
isolation_level="SERIALIZABLE",
connect_args={'check_same_thread': False},
poolclass=StaticPool)
with check_engine.begin() as connection:
connection.execute(text("attach database '{}' as calibre;".format(dbpath)))
connection.execute(text("attach database '{}' as app_settings;".format(app_db_path)))
check_engine.connect()
except Exception:
return False
return True
@classmethod
def update_config(cls, config):
cls.config = config cls.config = config
@classmethod
def setup_db(cls, config_calibre_dir, app_db_path):
# cls.config = config
cls.dispose() cls.dispose()
# toDo: if db changed -> delete shelfs, delete download books, delete read boks, kobo sync?? # toDo: if db changed -> delete shelfs, delete download books, delete read boks, kobo sync??
if not config.config_calibre_dir: if not config_calibre_dir:
config.invalidate() cls.config.invalidate()
return False return False
dbpath = os.path.join(config.config_calibre_dir, "metadata.db") dbpath = os.path.join(config_calibre_dir, "metadata.db")
if not os.path.exists(dbpath): if not os.path.exists(dbpath):
config.invalidate() cls.config.invalidate()
return False return False
try: try:
...@@ -552,10 +577,10 @@ class CalibreDB(): ...@@ -552,10 +577,10 @@ class CalibreDB():
conn = cls.engine.connect() conn = cls.engine.connect()
# conn.text_factory = lambda b: b.decode(errors = 'ignore') possible fix for #1302 # conn.text_factory = lambda b: b.decode(errors = 'ignore') possible fix for #1302
except Exception as ex: except Exception as ex:
config.invalidate(ex) cls.config.invalidate(ex)
return False return False
config.db_configured = True cls.config.db_configured = True
if not cc_classes: if not cc_classes:
try: try:
...@@ -828,7 +853,8 @@ class CalibreDB(): ...@@ -828,7 +853,8 @@ class CalibreDB():
def reconnect_db(self, config, app_db_path): def reconnect_db(self, config, app_db_path):
self.dispose() self.dispose()
self.engine.dispose() self.engine.dispose()
self.setup_db(config, app_db_path) self.setup_db(config.config_calibre_dir, app_db_path)
self.update_config(config)
def lcase(s): def lcase(s):
......
...@@ -74,7 +74,7 @@ def google_drive_callback(): ...@@ -74,7 +74,7 @@ def google_drive_callback():
f.write(credentials.to_json()) f.write(credentials.to_json())
except (ValueError, AttributeError) as error: except (ValueError, AttributeError) as error:
log.error(error) log.error(error)
return redirect(url_for('admin.configuration')) return redirect(url_for('admin.db_configuration'))
@gdrive.route("/watch/subscribe") @gdrive.route("/watch/subscribe")
...@@ -99,7 +99,7 @@ def watch_gdrive(): ...@@ -99,7 +99,7 @@ def watch_gdrive():
else: else:
flash(reason['message'], category="error") flash(reason['message'], category="error")
return redirect(url_for('admin.configuration')) return redirect(url_for('admin.db_configuration'))
@gdrive.route("/watch/revoke") @gdrive.route("/watch/revoke")
...@@ -115,7 +115,7 @@ def revoke_watch_gdrive(): ...@@ -115,7 +115,7 @@ def revoke_watch_gdrive():
pass pass
config.config_google_drive_watch_changes_response = {} config.config_google_drive_watch_changes_response = {}
config.save() config.save()
return redirect(url_for('admin.configuration')) return redirect(url_for('admin.db_configuration'))
@gdrive.route("/watch/callback", methods=['GET', 'POST']) @gdrive.route("/watch/callback", methods=['GET', 'POST'])
......
...@@ -141,7 +141,7 @@ function confirmDialog(id, dialogid, dataValue, yesFn, noFn) { ...@@ -141,7 +141,7 @@ function confirmDialog(id, dialogid, dataValue, yesFn, noFn) {
$confirm.modal("hide"); $confirm.modal("hide");
}); });
$.ajax({ $.ajax({
method:"get", method:"post",
dataType: "json", dataType: "json",
url: getPath() + "/ajax/loaddialogtexts/" + id, url: getPath() + "/ajax/loaddialogtexts/" + id,
success: function success(data) { success: function success(data) {
...@@ -179,18 +179,6 @@ $("#delete_confirm").click(function() { ...@@ -179,18 +179,6 @@ $("#delete_confirm").click(function() {
} }
}); });
$("#books-table").bootstrapTable("refresh"); $("#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 {
...@@ -218,8 +206,6 @@ $("#deleteModal").on("show.bs.modal", function(e) { ...@@ -218,8 +206,6 @@ $("#deleteModal").on("show.bs.modal", function(e) {
$(e.currentTarget).find("#delete_confirm").data("ajax", $(e.relatedTarget).data("ajax")); $(e.currentTarget).find("#delete_confirm").data("ajax", $(e.relatedTarget).data("ajax"));
}); });
$(function() { $(function() {
var updateTimerID; var updateTimerID;
var updateText; var updateText;
...@@ -556,6 +542,86 @@ $(function() { ...@@ -556,6 +542,86 @@ $(function() {
this.closest("form").submit(); this.closest("form").submit();
}); });
function handle_response(data) {
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>');
});
}
}
$('.collapse').on('shown.bs.collapse', function(){
$(this).parent().find(".glyphicon-plus").removeClass("glyphicon-plus").addClass("glyphicon-minus");
}).on('hidden.bs.collapse', function(){
$(this).parent().find(".glyphicon-minus").removeClass("glyphicon-minus").addClass("glyphicon-plus");
});
function changeDbSettings() {
$("#db_submit").closest('form').submit();
}
$("#db_submit").click(function(e) {
e.preventDefault();
e.stopPropagation();
this.blur();
$.ajax({
method:"post",
dataType: "json",
url: window.location.pathname + "/../../ajax/simulatedbchange",
data: {config_calibre_dir: $("#config_calibre_dir").val()},
success: function success(data) {
if ( data.change ) {
if ( data.valid ) {
confirmDialog(
"db_submit",
"GeneralChangeModal",
0,
changeDbSettings
);
}
else {
$("#InvalidDialog").modal('show');
}
} else {
changeDbSettings();
}
}
});
});
$("#config_submit").click(function(e) {
e.preventDefault();
e.stopPropagation();
this.blur();
window.scrollTo({top: 0, behavior: 'smooth'});
var request_path = "/../../admin/ajaxconfig";
var loader = "/../..";
$("#flash_success").remove();
$("#flash_danger").remove();
$.post(window.location.pathname + request_path, $(this).closest("form").serialize(), function(data) {
$('#config_upload_formats').val(data.config_upload);
if(data.reboot) {
$("#spinning_success").show();
var rebootInterval = setInterval(function(){
$.get({
url:window.location.pathname + "/../../admin/alive",
success: function (d, statusText, xhr) {
if (xhr.status < 400) {
$("#spinning_success").hide();
clearInterval(rebootInterval);
handle_response(data.result);
}
},
});
}, 1000);
} else {
handle_response(data.result);
}
});
});
$("#delete_shelf").click(function() { $("#delete_shelf").click(function() {
confirmDialog( confirmDialog(
$(this).attr('id'), $(this).attr('id'),
...@@ -568,7 +634,6 @@ $(function() { ...@@ -568,7 +634,6 @@ $(function() {
}); });
$("#fileModal").on("show.bs.modal", function(e) { $("#fileModal").on("show.bs.modal", function(e) {
var target = $(e.relatedTarget); var target = $(e.relatedTarget);
var path = $("#" + target.data("link"))[0].value; var path = $("#" + target.data("link"))[0].value;
...@@ -632,7 +697,6 @@ $(function() { ...@@ -632,7 +697,6 @@ $(function() {
$(".update-view").click(function(e) { $(".update-view").click(function(e) {
var view = $(this).data("view"); var view = $(this).data("view");
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
$.ajax({ $.ajax({
......
...@@ -150,6 +150,7 @@ ...@@ -150,6 +150,7 @@
</div> </div>
{% endif %} {% endif %}
</div> </div>
<a class="btn btn-default" id="db_config" href="{{url_for('admin.db_configuration')}}">{{_('Edit Calibre Database Configuration')}}</a>
<a class="btn btn-default" id="basic_config" href="{{url_for('admin.configuration')}}">{{_('Edit Basic Configuration')}}</a> <a class="btn btn-default" id="basic_config" href="{{url_for('admin.configuration')}}">{{_('Edit Basic Configuration')}}</a>
<a class="btn btn-default" id="view_config" href="{{url_for('admin.view_configuration')}}">{{_('Edit UI Configuration')}}</a> <a class="btn btn-default" id="view_config" href="{{url_for('admin.view_configuration')}}">{{_('Edit UI Configuration')}}</a>
</div> </div>
......
{% extends "layout.html" %}
{% block flash %}
<div id="spinning_success" class="row-fluid text-center" style="margin-top: -20px; display:none;">
<div class="alert alert-info"><img id="img-spinner" src="{{ url_for('static', filename='css/libs/images/loading-icon.gif') }}"/></div>
</div>
{% endblock %}
{% block body %}
<div class="discover">
<h2>{{title}}</h2>
<form role="form" method="POST" class="col-md-10 col-lg-6" action="{{ url_for('admin.db_configuration') }}" autocomplete="off">
<label for="config_calibre_dir">{{_('Location of Calibre Database')}}</label>
<div class="form-group required input-group">
<input type="text" class="form-control" id="config_calibre_dir" name="config_calibre_dir" value="{% if config.config_calibre_dir != None %}{{ config.config_calibre_dir }}{% endif %}" autocomplete="off">
<span class="input-group-btn">
<button type="button" data-toggle="modal" id="calibre_modal_path" data-link="config_calibre_dir" data-filefilter="metadata.db" data-target="#fileModal" id="library_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
</span>
</div>
{% if feature_support['gdrive'] %}
<div class="form-group required">
<input type="checkbox" id="config_use_google_drive" name="config_use_google_drive" data-control="gdrive_settings" {% if config.config_use_google_drive %}checked{% endif %} >
<label for="config_use_google_drive">{{_('Use Google Drive?')}}</label>
</div>
{% if not gdriveError %}
{% if show_authenticate_google_drive and config.config_use_google_drive %}
<div class="form-group required">
<a href="{{ url_for('gdrive.authenticate_google_drive') }}" id="gdrive_auth" class="btn btn-primary">{{_('Authenticate Google Drive')}}</a>
</div>
{% else %}
{% if not show_authenticate_google_drive %}
<div class="form-group required">
<label for="config_google_drive_folder">{{_('Google Drive Calibre folder')}}</label>
<select name="config_google_drive_folder" id="config_google_drive_folder" class="form-control">
{% for gdrivefolder in gdrivefolders %}
<option value="{{ gdrivefolder.title }}" {% if gdrivefolder.title == config.config_google_drive_folder %}selected{% endif %}>{{ gdrivefolder.title }}</option>
{% endfor %}
</select>
</div>
{% if config.config_google_drive_watch_changes_response %}
<label for="config_google_drive_watch_changes_response">{{_('Metadata Watch Channel ID')}}</label>
<div class="form-group input-group required">
<input type="text" class="form-control" name="config_google_drive_watch_changes_response" id="config_google_drive_watch_changes_response" value="{{ config.config_google_drive_watch_changes_response['id'] }} expires on {{ config.config_google_drive_watch_changes_response['expiration'] | strftime }}" autocomplete="off" disabled="">
<span class="input-group-btn"><a href="{{ url_for('gdrive.revoke_watch_gdrive') }}" id="watch_revoke" class="btn btn-primary">{{_('Revoke')}}</a></span>
</div>
{% else %}
<a href="{{ url_for('gdrive.watch_gdrive') }}" id="enable_gdrive_watch" class="btn btn-primary">Enable watch of metadata.db</a>
{% endif %}
{% endif %}
{% endif %}
{% endif %}
{% endif %}
<div class="col-sm-12">
<div id="db_submit" name="submit" class="btn btn-default">{{_('Save')}}</div>
<a href="{{ url_for('admin.admin') }}" id="config_back" class="btn btn-default">{{_('Cancel')}}</a>
</div>
</form>
</div>
{% endblock %}
{% block modal %}
{{ filechooser_modal() }}
{{ change_confirm_modal() }}
<div id="InvalidDialog" class="modal fade" role="dialog">
<div class="modal-dialog modal-sm">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header bg-info"></div>
<div class="modal-body text-center">
<p>{{_('New db location is invalid, please enter valid path')}}</p>
<p></p>
<button type="button" class="btn btn-default" id="invalid_confirm" data-dismiss="modal">{{_('OK')}}</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% extends "layout.html" %} {% extends "layout.html" %}
{% block flash %}
<div id="spinning_success" class="row-fluid text-center" style="margin-top: -20px; display:none;">
<div class="alert alert-info"><img id="img-spinner" src="{{ url_for('static', filename='css/libs/images/loading-icon.gif') }}"/></div>
</div>
{% endblock %}
{% block body %} {% block body %}
<div class="discover"> <div class="discover">
<h2>{{title}}</h2> <h2>{{title}}</h2>
<form role="form" method="POST" autocomplete="off"> <form role="form" method="POST" autocomplete="off">
<div class="panel-group col-md-10 col-lg-6"> <div class="panel-group col-md-10 col-lg-6">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h4 class="panel-title"> <h4 class="panel-title">
<a class="accordion-toggle" data-toggle="collapse" href="#collapseOne"> <a class="accordion-toggle" data-toggle="collapse" href="#collapseone">
<span class="glyphicon glyphicon-minus"></span>
{{_('Library Configuration')}}
</a>
</h4>
</div>
<div id="collapseOne" class="panel-collapse collapse in">
<div class="panel-body">
<label for="config_calibre_dir">{{_('Location of Calibre Database')}}</label>
<div class="form-group required{% if filepicker %} input-group{% endif %}">
<input type="text" class="form-control" id="config_calibre_dir" name="config_calibre_dir" value="{% if config.config_calibre_dir != None %}{{ config.config_calibre_dir }}{% endif %}" autocomplete="off">
{% if filepicker %}
<span class="input-group-btn">
<button type="button" data-toggle="modal" id="calibre_modal_path" data-link="config_calibre_dir" data-filefilter="metadata.db" data-target="#fileModal" id="library_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
</span>
{% endif %}
</div>
{% if not filepicker %}
<div class="form-group">
<label id="filepicker-hint">{{_('To activate serverside filepicker start Calibre-Web with -f option')}}</label>
</div>
{% endif %}
{% if feature_support['gdrive'] %}
<div class="form-group required">
<input type="checkbox" id="config_use_google_drive" name="config_use_google_drive" data-control="gdrive_settings" {% if config.config_use_google_drive %}checked{% endif %} >
<label for="config_use_google_drive">{{_('Use Google Drive?')}}</label>
</div>
<div data-related="gdrive_settings">
{% if gdriveError %}
<div class="form-group">
<label id="gdrive_error">
{{_('Google Drive config problem')}}: {{ gdriveError }}
</label>
</div>
{% else %}
{% if show_authenticate_google_drive and g.user.is_authenticated and config.config_use_google_drive %}
<div class="form-group required">
<a href="{{ url_for('gdrive.authenticate_google_drive') }}" id="gdrive_auth" class="btn btn-primary">{{_('Authenticate Google Drive')}}</a>
</div>
{% else %}
{% if show_authenticate_google_drive and g.user.is_authenticated and not config.config_use_google_drive %}
<div >{{_('Please hit save to continue with setup')}}</div>
{% endif %}
{% if not g.user.is_authenticated and show_login_button %}
<div >{{_('Please finish Google Drive setup after login')}}</div>
{% endif %}
{% if g.user.is_authenticated %}
{% if not show_authenticate_google_drive %}
<div class="form-group required">
<label for="config_google_drive_folder">{{_('Google Drive Calibre folder')}}</label>
<select name="config_google_drive_folder" id="config_google_drive_folder" class="form-control">
{% for gdrivefolder in gdrivefolders %}
<option value="{{ gdrivefolder.title }}" {% if gdrivefolder.title == config.config_google_drive_folder %}selected{% endif %}>{{ gdrivefolder.title }}</option>
{% endfor %}
</select>
</div>
{% if config.config_google_drive_watch_changes_response %}
<label for="config_google_drive_watch_changes_response">{{_('Metadata Watch Channel ID')}}</label>
<div class="form-group input-group required">
<input type="text" class="form-control" name="config_google_drive_watch_changes_response" id="config_google_drive_watch_changes_response" value="{{ config.config_google_drive_watch_changes_response['id'] }} expires on {{ config.config_google_drive_watch_changes_response['expiration'] | strftime }}" autocomplete="off" disabled="">
<span class="input-group-btn"><a href="{{ url_for('gdrive.revoke_watch_gdrive') }}" id="watch_revoke" class="btn btn-primary">{{_('Revoke')}}</a></span>
</div>
{% else %}
<a href="{{ url_for('gdrive.watch_gdrive') }}" id="enable_gdrive_watch" class="btn btn-primary">Enable watch of metadata.db</a>
{% endif %}
{% endif %}
{% endif %}
{% endif %}
{% endif %}
</div>
{% endif %}
</div>
</div>
</div>
{% if show_back_button %}
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a class="accordion-toggle" data-toggle="collapse" href="#collapsetwo">
<span class="glyphicon glyphicon-plus"></span> <span class="glyphicon glyphicon-plus"></span>
{{_('Server Configuration')}} {{_('Server Configuration')}}
</a> </a>
</h4> </h4>
</div> </div>
<div id="collapsetwo" class="panel-collapse collapse"> <div id="collapseone" class="panel-collapse collapse">
<div class="panel-body"> <div class="panel-body">
<div class="form-group"> <div class="form-group">
<label for="config_port">{{_('Server Port')}}</label> <label for="config_port">{{_('Server Port')}}</label>
...@@ -124,13 +51,13 @@ ...@@ -124,13 +51,13 @@
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h4 class="panel-title"> <h4 class="panel-title">
<a class="accordion-toggle" data-toggle="collapse" href="#collapsethree"> <a class="accordion-toggle" data-toggle="collapse" href="#collapsetwo">
<span class="glyphicon glyphicon-plus"></span> <span class="glyphicon glyphicon-plus"></span>
{{_('Logfile Configuration')}} {{_('Logfile Configuration')}}
</a> </a>
</h4> </h4>
</div> </div>
<div id="collapsethree" class="panel-collapse collapse"> <div id="collapsetwo" class="panel-collapse collapse">
<div class="panel-body"> <div class="panel-body">
<div class="form-group"> <div class="form-group">
<label for="config_log_level">{{_('Log Level')}}</label> <label for="config_log_level">{{_('Log Level')}}</label>
...@@ -159,13 +86,13 @@ ...@@ -159,13 +86,13 @@
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h4 class="panel-title"> <h4 class="panel-title">
<a class="accordion-toggle" data-toggle="collapse" href="#collapsefive"> <a class="accordion-toggle" data-toggle="collapse" href="#collapsefour">
<span class="glyphicon glyphicon-plus"></span> <span class="glyphicon glyphicon-plus"></span>
{{_('Feature Configuration')}} {{_('Feature Configuration')}}
</a> </a>
</h4> </h4>
</div> </div>
<div id="collapsefive" class="panel-collapse collapse"> <div id="collapsefour" class="panel-collapse collapse">
<div class="panel-body"> <div class="panel-body">
<div class="form-group"> <div class="form-group">
<input type="checkbox" id="config_uploading" data-control="upload_settings" name="config_uploading" {% if config.config_uploading %}checked{% endif %}> <input type="checkbox" id="config_uploading" data-control="upload_settings" name="config_uploading" {% if config.config_uploading %}checked{% endif %}>
...@@ -379,13 +306,13 @@ ...@@ -379,13 +306,13 @@
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h4 class="panel-title"> <h4 class="panel-title">
<a class="accordion-toggle" data-toggle="collapse" href="#collapseeight"> <a class="accordion-toggle" data-toggle="collapse" href="#collapsefive">
<span class="glyphicon glyphicon-plus"></span> <span class="glyphicon glyphicon-plus"></span>
{{_('External binaries')}} {{_('External binaries')}}
</a> </a>
</h4> </h4>
</div> </div>
<div id="collapseeight" class="panel-collapse collapse"> <div id="collapsefive" class="panel-collapse collapse">
<div class="panel-body"> <div class="panel-body">
<label for="config_converterpath">{{_('Path to Calibre E-Book Converter')}}</label> <label for="config_converterpath">{{_('Path to Calibre E-Book Converter')}}</label>
<div class="form-group input-group"> <div class="form-group input-group">
...@@ -417,18 +344,10 @@ ...@@ -417,18 +344,10 @@
</div> </div>
</div> </div>
</div> </div>
{% endif %}
</div> </div>
<div class="col-sm-12"> <div class="col-sm-12">
{% if not show_login_button %} <button type="button" name="submit" id="config_submit" class="btn btn-default">{{_('Save')}}</button>
<button type="submit" name="submit" class="btn btn-default">{{_('Save')}}</button> <a href="{{ url_for('admin.admin') }}" id="config_back" class="btn btn-default">{{_('Cancel')}}</a>
{% endif %}
{% if show_back_button %}
<a href="{{ url_for('admin.admin') }}" class="btn btn-default">{{_('Cancel')}}</a>
{% endif %}
{% if show_login_button %}
<a href="{{ url_for('web.login') }}" name="login" class="btn btn-default">{{_('Login')}}</a>
{% endif %}
</div> </div>
</form> </form>
</div> </div>
...@@ -436,15 +355,3 @@ ...@@ -436,15 +355,3 @@
{% block modal %} {% block modal %}
{{ filechooser_modal() }} {{ filechooser_modal() }}
{% endblock %} {% endblock %}
{% block js %}
<script type="text/javascript">
$(document).on('change', '#config_use_google_drive', function() {
$('#config_google_drive_folder').prop('required', $(this).prop('checked'));
});
$('.collapse').on('shown.bs.collapse', function(){
$(this).parent().find(".glyphicon-plus").removeClass("glyphicon-plus").addClass("glyphicon-minus");
}).on('hidden.bs.collapse', function(){
$(this).parent().find(".glyphicon-minus").removeClass("glyphicon-minus").addClass("glyphicon-plus");
});
</script>
{% endblock %}
...@@ -6,8 +6,8 @@ ...@@ -6,8 +6,8 @@
{% block body %} {% block body %}
<div class="discover"> <div class="discover">
<h2>{{title}}</h2> <h2>{{title}}</h2>
<form role="form" method="POST" autocomplete="off" class="col-md-10 col-lg-6"> <form role="form" method="POST" autocomplete="off" >
<div class="panel-group"> <div class="panel-group class="col-md-10 col-lg-6">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h4 class="panel-title"> <h4 class="panel-title">
...@@ -71,7 +71,6 @@ ...@@ -71,7 +71,6 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h4 class="panel-title"> <h4 class="panel-title">
...@@ -146,6 +145,7 @@ ...@@ -146,6 +145,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="col-sm-12"> <div class="col-sm-12">
<button type="submit" name="submit" class="btn btn-default">{{_('Save')}}</button> <button type="submit" name="submit" class="btn btn-default">{{_('Save')}}</button>
<a href="{{ url_for('admin.admin') }}" class="btn btn-default">{{_('Cancel')}}</a> <a href="{{ url_for('admin.admin') }}" class="btn btn-default">{{_('Cancel')}}</a>
...@@ -157,13 +157,6 @@ ...@@ -157,13 +157,6 @@
{{ restrict_modal() }} {{ restrict_modal() }}
{% endblock %} {% endblock %}
{% block js %} {% block js %}
<script type="text/javascript">
$('.collapse').on('shown.bs.collapse', function(){
$(this).parent().find(".glyphicon-plus").removeClass("glyphicon-plus").addClass("glyphicon-minus");
}).on('hidden.bs.collapse', function(){
$(this).parent().find(".glyphicon-minus").removeClass("glyphicon-minus").addClass("glyphicon-plus");
});
</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.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>
......
...@@ -111,6 +111,7 @@ ...@@ -111,6 +111,7 @@
</div> </div>
{%endif%} {%endif%}
{% endfor %} {% endfor %}
{% block flash %}{% endblock %}
{% if g.current_theme == 1 %} {% if g.current_theme == 1 %}
<div id="loader" hidden="true"> <div id="loader" hidden="true">
<center> <center>
......
...@@ -188,7 +188,7 @@ class User(UserBase, Base): ...@@ -188,7 +188,7 @@ class User(UserBase, Base):
allowed_column_value = Column(String, default="") allowed_column_value = Column(String, default="")
remote_auth_token = relationship('RemoteAuthToken', backref='user', lazy='dynamic') remote_auth_token = relationship('RemoteAuthToken', backref='user', lazy='dynamic')
view_settings = Column(JSON, default={}) view_settings = Column(JSON, default={})
kobo_only_shelves_sync = Column(Integer, default=1) kobo_only_shelves_sync = Column(Integer, default=0)
if oauth_support: if oauth_support:
......
...@@ -1381,10 +1381,14 @@ def serve_book(book_id, book_format, anyname): ...@@ -1381,10 +1381,14 @@ def serve_book(book_id, book_format, anyname):
return "File not in Database" return "File not in Database"
log.info('Serving book: %s', data.name) log.info('Serving book: %s', data.name)
if config.config_use_google_drive: if config.config_use_google_drive:
headers = Headers() try:
headers["Content-Type"] = mimetypes.types_map.get('.' + book_format, "application/octet-stream") headers = Headers()
df = getFileFromEbooksFolder(book.path, data.name + "." + book_format) headers["Content-Type"] = mimetypes.types_map.get('.' + book_format, "application/octet-stream")
return do_gdrive_download(df, headers, (book_format.upper() == 'TXT')) df = getFileFromEbooksFolder(book.path, data.name + "." + book_format)
return do_gdrive_download(df, headers, (book_format.upper() == 'TXT'))
except AttributeError as ex:
log.debug_or_exception(ex)
return "File Not Found"
else: else:
if book_format.upper() == 'TXT': if book_format.upper() == 'TXT':
try: try:
...@@ -1394,11 +1398,11 @@ def serve_book(book_id, book_format, anyname): ...@@ -1394,11 +1398,11 @@ def serve_book(book_id, book_format, anyname):
return make_response( return make_response(
rawdata.decode(result['encoding']).encode('utf-8')) rawdata.decode(result['encoding']).encode('utf-8'))
except FileNotFoundError: except FileNotFoundError:
log.error("File Not Found")
return "File Not Found" return "File Not Found"
return send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + book_format) return send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + book_format)
@web.route("/download/<int:book_id>/<book_format>", defaults={'anyname': 'None'}) @web.route("/download/<int:book_id>/<book_format>", defaults={'anyname': 'None'})
@web.route("/download/<int:book_id>/<book_format>/<anyname>") @web.route("/download/<int:book_id>/<book_format>/<anyname>")
@login_required_if_no_ano @login_required_if_no_ano
...@@ -1489,9 +1493,9 @@ def register(): ...@@ -1489,9 +1493,9 @@ def register():
@web.route('/login', methods=['GET', 'POST']) @web.route('/login', methods=['GET', 'POST'])
def login(): def login():
if not config.db_configured: #if not config.db_configured:
log.debug(u"Redirect to initial configuration") # log.debug(u"Redirect to initial configuration")
return redirect(url_for('admin.basic_configuration')) # return redirect(url_for('admin.basic_configuration'))
if current_user is not None and current_user.is_authenticated: if current_user is not None and current_user.is_authenticated:
return redirect(url_for('web.index')) return redirect(url_for('web.index'))
if config.config_login_type == constants.LOGIN_LDAP and not services.ldap: if config.config_login_type == constants.LOGIN_LDAP and not services.ldap:
......
...@@ -3,7 +3,7 @@ Flask-Babel>=0.11.1,<2.1.0 ...@@ -3,7 +3,7 @@ Flask-Babel>=0.11.1,<2.1.0
Flask-Login>=0.3.2,<0.5.1 Flask-Login>=0.3.2,<0.5.1
Flask-Principal>=0.3.2,<0.5.1 Flask-Principal>=0.3.2,<0.5.1
backports_abc>=0.4 backports_abc>=0.4
Flask>=1.0.2,<2.0.0 Flask>=1.0.2,<2.1.0
iso-639>=0.4.5,<0.5.0 iso-639>=0.4.5,<0.5.0
PyPDF3>=1.0.0,<1.0.4 PyPDF3>=1.0.0,<1.0.4
pytz>=2016.10 pytz>=2016.10
......
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment