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)
This commit is contained in:
parent
dcdb5e2a9e
commit
a47d6cd937
@ -83,7 +83,9 @@ log = logger.create()
|
||||
|
||||
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()
|
||||
|
||||
|
275
cps/admin.py
275
cps/admin.py
@ -40,7 +40,7 @@ from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError
|
||||
from sqlalchemy.sql.expression import func, or_, text
|
||||
|
||||
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 .helper import check_valid_domain, send_test_mail, reset_password, generate_password_hash, check_email, \
|
||||
valid_email, check_username
|
||||
@ -97,19 +97,6 @@ def admin_required(f):
|
||||
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
|
||||
def before_request():
|
||||
if current_user.is_authenticated:
|
||||
@ -124,10 +111,14 @@ def before_request():
|
||||
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()
|
||||
if '/static/' not in request.path and not config.db_configured and \
|
||||
request.endpoint not in ('admin.basic_configuration',
|
||||
'login',
|
||||
'admin.config_pathchooser'):
|
||||
return redirect(url_for('admin.basic_configuration'))
|
||||
request.endpoint not in ('admin.ajax_db_config',
|
||||
'admin.simulatedbchange',
|
||||
'admin.db_configuration',
|
||||
'web.login',
|
||||
'web.logout',
|
||||
'admin.load_dialogtexts',
|
||||
'admin.ajax_pathchooser'):
|
||||
return redirect(url_for('admin.db_configuration'))
|
||||
|
||||
|
||||
@admi.route("/admin")
|
||||
@ -194,16 +185,46 @@ def admin():
|
||||
feature_support=feature_support, kobo_support=kobo_support,
|
||||
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
|
||||
@admin_required
|
||||
def configuration():
|
||||
if request.method == "POST":
|
||||
return _configuration_update_helper(True)
|
||||
return _configuration_result()
|
||||
return render_title_template("config_edit.html",
|
||||
config=config,
|
||||
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")
|
||||
@login_required
|
||||
@admin_required
|
||||
@ -539,10 +560,10 @@ def update_view_configuration():
|
||||
return view_configuration()
|
||||
|
||||
|
||||
@admi.route("/ajax/loaddialogtexts/<element_id>")
|
||||
@admi.route("/ajax/loaddialogtexts/<element_id>", methods=['POST'])
|
||||
@login_required
|
||||
def load_dialogtexts(element_id):
|
||||
texts = {"header": "", "main": ""}
|
||||
texts = {"header": "", "main": "", "valid": 1}
|
||||
if element_id == "config_delete_kobo_token":
|
||||
texts["main"] = _('Do you really want to delete the Kobo Token?')
|
||||
elif element_id == "btndeletedomain":
|
||||
@ -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)?')
|
||||
elif element_id == "kobo_only_shelves_sync":
|
||||
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)
|
||||
|
||||
|
||||
@ -867,14 +890,6 @@ def list_restriction(res_type, user_id):
|
||||
return response
|
||||
|
||||
|
||||
@admi.route("/basicconfig/pathchooser/")
|
||||
@unconfigured
|
||||
def config_pathchooser():
|
||||
if filepicker:
|
||||
return pathchooser()
|
||||
abort(403)
|
||||
|
||||
|
||||
@admi.route("/ajax/pathchooser/")
|
||||
@login_required
|
||||
@admin_required
|
||||
@ -963,16 +978,6 @@ def pathchooser():
|
||||
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):
|
||||
return config.set_from_dictionary(to_save, x, func)
|
||||
|
||||
@ -991,23 +996,24 @@ def _config_string(to_save, x):
|
||||
|
||||
def _configuration_gdrive_helper(to_save):
|
||||
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 "config_use_google_drive" in to_save and not config.config_use_google_drive and not gdrive_error:
|
||||
with open(gdriveutils.CLIENT_SECRETS, 'r') as settings:
|
||||
gdrive_secrets = json.load(settings)['web']
|
||||
if not gdrive_secrets:
|
||||
return _configuration_result(_('client_secrets.json Is Not Configured For Web Application'))
|
||||
gdriveutils.update_settings(
|
||||
gdrive_secrets['client_id'],
|
||||
gdrive_secrets['client_secret'],
|
||||
gdrive_secrets['redirect_uris'][0]
|
||||
)
|
||||
if gdrive_support:
|
||||
gdrive_error = gdriveutils.get_error_text(gdrive_secrets)
|
||||
if "config_use_google_drive" in to_save and not config.config_use_google_drive and not gdrive_error:
|
||||
with open(gdriveutils.CLIENT_SECRETS, 'r') as settings:
|
||||
gdrive_secrets = json.load(settings)['web']
|
||||
if not gdrive_secrets:
|
||||
return _configuration_result(_('client_secrets.json Is Not Configured For Web Application'))
|
||||
gdriveutils.update_settings(
|
||||
gdrive_secrets['client_id'],
|
||||
gdrive_secrets['client_secret'],
|
||||
gdrive_secrets['redirect_uris'][0]
|
||||
)
|
||||
|
||||
# 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)
|
||||
@ -1041,23 +1047,23 @@ def _configuration_oauth_helper(to_save):
|
||||
return reboot_required
|
||||
|
||||
|
||||
def _configuration_logfile_helper(to_save, gdrive_error):
|
||||
def _configuration_logfile_helper(to_save):
|
||||
reboot_required = False
|
||||
reboot_required |= _config_int(to_save, "config_log_level")
|
||||
reboot_required |= _config_string(to_save, "config_logfile")
|
||||
if not logger.is_valid_logfile(config.config_logfile):
|
||||
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_string(to_save, "config_access_logfile")
|
||||
if not logger.is_valid_logfile(config.config_access_logfile):
|
||||
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
|
||||
|
||||
|
||||
def _configuration_ldap_helper(to_save, gdrive_error):
|
||||
def _configuration_ldap_helper(to_save):
|
||||
reboot_required = False
|
||||
reboot_required |= _config_string(to_save, "config_ldap_provider_url")
|
||||
reboot_required |= _config_int(to_save, "config_ldap_port")
|
||||
@ -1084,44 +1090,37 @@ def _configuration_ldap_helper(to_save, gdrive_error):
|
||||
or not config.config_ldap_dn \
|
||||
or not config.config_ldap_user_object:
|
||||
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_UNAUTHENTICATE:
|
||||
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',
|
||||
gdrive_error)
|
||||
return reboot_required, _configuration_result(_('Please Enter a LDAP Service Account and Password'))
|
||||
else:
|
||||
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.count("%s") != 1:
|
||||
return reboot_required, \
|
||||
_configuration_result(_('LDAP Group Object Filter Needs to Have One "%s" Format Identifier'),
|
||||
gdrive_error)
|
||||
_configuration_result(_('LDAP Group Object Filter Needs to Have One "%s" Format Identifier'))
|
||||
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'),
|
||||
gdrive_error)
|
||||
return reboot_required, _configuration_result(_('LDAP Group Object Filter Has Unmatched Parenthesis'))
|
||||
|
||||
if config.config_ldap_user_object.count("%s") != 1:
|
||||
return reboot_required, \
|
||||
_configuration_result(_('LDAP User Object Filter needs to Have One "%s" Format Identifier'),
|
||||
gdrive_error)
|
||||
_configuration_result(_('LDAP User Object Filter needs to Have One "%s" Format Identifier'))
|
||||
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'),
|
||||
gdrive_error)
|
||||
return reboot_required, _configuration_result(_('LDAP User Object Filter Has Unmatched Parenthesis'))
|
||||
|
||||
if to_save.get("ldap_import_user_filter") == '0':
|
||||
config.config_ldap_member_user_object = ""
|
||||
else:
|
||||
if config.config_ldap_member_user_object.count("%s") != 1:
|
||||
return reboot_required, \
|
||||
_configuration_result(_('LDAP Member User Filter needs to Have One "%s" Format Identifier'),
|
||||
gdrive_error)
|
||||
_configuration_result(_('LDAP Member User Filter needs to Have One "%s" Format Identifier'))
|
||||
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'),
|
||||
gdrive_error)
|
||||
return reboot_required, _configuration_result(_('LDAP Member User Filter Has Unmatched Parenthesis'))
|
||||
|
||||
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
|
||||
@ -1129,13 +1128,31 @@ def _configuration_ldap_helper(to_save, gdrive_error):
|
||||
os.path.isfile(config.config_ldap_key_path)):
|
||||
return reboot_required, \
|
||||
_configuration_result(_('LDAP CACertificate, Certificate or Key Location is not Valid, '
|
||||
'Please Enter Correct Path'),
|
||||
gdrive_error)
|
||||
'Please Enter Correct Path'))
|
||||
return reboot_required, None
|
||||
|
||||
|
||||
def _configuration_update_helper(configured):
|
||||
reboot_required = False
|
||||
@admi.route("/ajax/simulatedbchange", methods=['POST'])
|
||||
@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
|
||||
to_save = request.form.to_dict()
|
||||
gdrive_error = None
|
||||
@ -1145,24 +1162,47 @@ def _configuration_update_helper(configured):
|
||||
to_save['config_calibre_dir'],
|
||||
flags=re.IGNORECASE)
|
||||
try:
|
||||
db_change |= _config_string(to_save, "config_calibre_dir")
|
||||
db_change, db_valid = _db_simulate_change()
|
||||
|
||||
# gdrive_error drive setup
|
||||
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_string(to_save, "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'),
|
||||
gdrive_error,
|
||||
configured)
|
||||
return _configuration_result(_('Keyfile Location is not Valid, Please Enter Correct Path'))
|
||||
|
||||
reboot_required |= _config_string(to_save, "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'),
|
||||
gdrive_error,
|
||||
configured)
|
||||
return _configuration_result(_('Certfile Location is not Valid, Please Enter Correct Path'))
|
||||
|
||||
_config_checkbox_int(to_save, "config_uploading")
|
||||
# Reboot on config_anonbrowse with enabled ldap, as decoraters are changed in this case
|
||||
@ -1186,15 +1226,14 @@ def _configuration_update_helper(configured):
|
||||
|
||||
reboot_required |= _config_int(to_save, "config_login_type")
|
||||
|
||||
# LDAP configurator,
|
||||
# LDAP configurator
|
||||
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:
|
||||
return message
|
||||
reboot_required |= reboot
|
||||
|
||||
# Remote login configuration
|
||||
|
||||
_config_checkbox(to_save, "config_remote_login")
|
||||
if not config.config_remote_login:
|
||||
ub.session.query(ub.RemoteAuthToken).filter(ub.RemoteAuthToken.token_type == 0).delete()
|
||||
@ -1218,7 +1257,7 @@ def _configuration_update_helper(configured):
|
||||
if config.config_login_type == constants.LOGIN_OAUTH:
|
||||
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:
|
||||
return message
|
||||
reboot_required |= reboot
|
||||
@ -1227,67 +1266,55 @@ def _configuration_update_helper(configured):
|
||||
if "config_rarfile_location" in to_save:
|
||||
unrar_status = helper.check_unrar(config.config_rarfile_location)
|
||||
if unrar_status:
|
||||
return _configuration_result(unrar_status, gdrive_error, configured)
|
||||
return _configuration_result(unrar_status)
|
||||
except (OperationalError, InvalidRequestError):
|
||||
ub.session.rollback()
|
||||
log.error("Settings DB is not Writeable")
|
||||
_configuration_result(_("Settings DB is not Writeable"), gdrive_error, configured)
|
||||
|
||||
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")
|
||||
_configuration_result(_("Settings DB is not Writeable"))
|
||||
|
||||
config.save()
|
||||
flash(_(u"Calibre-Web configuration updated"), category="success")
|
||||
if reboot_required:
|
||||
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()
|
||||
gdrivefolders = []
|
||||
if gdrive_error is None:
|
||||
if not gdrive_error and config.config_use_google_drive:
|
||||
gdrive_error = gdriveutils.get_error_text()
|
||||
if gdrive_error and gdrive_support:
|
||||
log.error(gdrive_error)
|
||||
gdrive_error = _(gdrive_error)
|
||||
flash(gdrive_error, category="error")
|
||||
else:
|
||||
if not gdrive_authenticate and gdrive_support:
|
||||
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:
|
||||
log.error(error_flash)
|
||||
config.load()
|
||||
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,
|
||||
provider=oauthblueprints,
|
||||
show_back_button=show_back_button,
|
||||
show_login_button=show_login_button,
|
||||
show_authenticate_google_drive=gdrive_authenticate,
|
||||
filepicker=configured,
|
||||
gdriveError=gdrive_error,
|
||||
gdrivefolders=gdrivefolders,
|
||||
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):
|
||||
|
@ -45,7 +45,6 @@ parser.add_argument('-v', '--version', action='version', help='Shows version num
|
||||
version=version_info())
|
||||
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('-f', action='store_true', help='Enables filepicker in unconfigured mode')
|
||||
args = parser.parse_args()
|
||||
|
||||
if sys.version_info < (3, 0):
|
||||
@ -114,6 +113,3 @@ user_credentials = args.s or None
|
||||
if user_credentials and ":" not in user_credentials:
|
||||
print("No valid 'username:password' format")
|
||||
sys.exit(3)
|
||||
|
||||
# Handles enabling of filepicker
|
||||
filepicker = args.f or None
|
||||
|
@ -347,7 +347,7 @@ class _ConfigSQL(object):
|
||||
log.error(error)
|
||||
log.warning("invalidating configuration")
|
||||
self.db_configured = False
|
||||
self.config_calibre_dir = None
|
||||
# self.config_calibre_dir = None
|
||||
self.save()
|
||||
|
||||
|
||||
|
42
cps/db.py
42
cps/db.py
@ -524,19 +524,44 @@ class CalibreDB():
|
||||
return cc_classes
|
||||
|
||||
@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
|
||||
|
||||
@classmethod
|
||||
def setup_db(cls, config_calibre_dir, app_db_path):
|
||||
# cls.config = config
|
||||
cls.dispose()
|
||||
|
||||
# toDo: if db changed -> delete shelfs, delete download books, delete read boks, kobo sync??
|
||||
|
||||
if not config.config_calibre_dir:
|
||||
config.invalidate()
|
||||
if not config_calibre_dir:
|
||||
cls.config.invalidate()
|
||||
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):
|
||||
config.invalidate()
|
||||
cls.config.invalidate()
|
||||
return False
|
||||
|
||||
try:
|
||||
@ -552,10 +577,10 @@ class CalibreDB():
|
||||
conn = cls.engine.connect()
|
||||
# conn.text_factory = lambda b: b.decode(errors = 'ignore') possible fix for #1302
|
||||
except Exception as ex:
|
||||
config.invalidate(ex)
|
||||
cls.config.invalidate(ex)
|
||||
return False
|
||||
|
||||
config.db_configured = True
|
||||
cls.config.db_configured = True
|
||||
|
||||
if not cc_classes:
|
||||
try:
|
||||
@ -828,7 +853,8 @@ class CalibreDB():
|
||||
def reconnect_db(self, config, app_db_path):
|
||||
self.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):
|
||||
|
@ -74,7 +74,7 @@ def google_drive_callback():
|
||||
f.write(credentials.to_json())
|
||||
except (ValueError, AttributeError) as error:
|
||||
log.error(error)
|
||||
return redirect(url_for('admin.configuration'))
|
||||
return redirect(url_for('admin.db_configuration'))
|
||||
|
||||
|
||||
@gdrive.route("/watch/subscribe")
|
||||
@ -99,7 +99,7 @@ def watch_gdrive():
|
||||
else:
|
||||
flash(reason['message'], category="error")
|
||||
|
||||
return redirect(url_for('admin.configuration'))
|
||||
return redirect(url_for('admin.db_configuration'))
|
||||
|
||||
|
||||
@gdrive.route("/watch/revoke")
|
||||
@ -115,7 +115,7 @@ def revoke_watch_gdrive():
|
||||
pass
|
||||
config.config_google_drive_watch_changes_response = {}
|
||||
config.save()
|
||||
return redirect(url_for('admin.configuration'))
|
||||
return redirect(url_for('admin.db_configuration'))
|
||||
|
||||
|
||||
@gdrive.route("/watch/callback", methods=['GET', 'POST'])
|
||||
|
@ -141,7 +141,7 @@ function confirmDialog(id, dialogid, dataValue, yesFn, noFn) {
|
||||
$confirm.modal("hide");
|
||||
});
|
||||
$.ajax({
|
||||
method:"get",
|
||||
method:"post",
|
||||
dataType: "json",
|
||||
url: getPath() + "/ajax/loaddialogtexts/" + id,
|
||||
success: function success(data) {
|
||||
@ -179,18 +179,6 @@ $("#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 {
|
||||
@ -218,8 +206,6 @@ $("#deleteModal").on("show.bs.modal", function(e) {
|
||||
$(e.currentTarget).find("#delete_confirm").data("ajax", $(e.relatedTarget).data("ajax"));
|
||||
});
|
||||
|
||||
|
||||
|
||||
$(function() {
|
||||
var updateTimerID;
|
||||
var updateText;
|
||||
@ -556,6 +542,86 @@ $(function() {
|
||||
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() {
|
||||
confirmDialog(
|
||||
$(this).attr('id'),
|
||||
@ -568,7 +634,6 @@ $(function() {
|
||||
|
||||
});
|
||||
|
||||
|
||||
$("#fileModal").on("show.bs.modal", function(e) {
|
||||
var target = $(e.relatedTarget);
|
||||
var path = $("#" + target.data("link"))[0].value;
|
||||
@ -632,7 +697,6 @@ $(function() {
|
||||
|
||||
$(".update-view").click(function(e) {
|
||||
var view = $(this).data("view");
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
$.ajax({
|
||||
|
@ -150,6 +150,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</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="view_config" href="{{url_for('admin.view_configuration')}}">{{_('Edit UI Configuration')}}</a>
|
||||
</div>
|
||||
|
74
cps/templates/config_db.html
Normal file
74
cps/templates/config_db.html
Normal file
@ -0,0 +1,74 @@
|
||||
{% 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 %}
|
@ -1,97 +1,24 @@
|
||||
{% 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" autocomplete="off">
|
||||
<form role="form" method="POST" autocomplete="off">
|
||||
<div class="panel-group col-md-10 col-lg-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">
|
||||
<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">
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#collapseone">
|
||||
<span class="glyphicon glyphicon-plus"></span>
|
||||
{{_('Server Configuration')}}
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="collapsetwo" class="panel-collapse collapse">
|
||||
<div id="collapseone" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
<div class="form-group">
|
||||
<label for="config_port">{{_('Server Port')}}</label>
|
||||
@ -124,13 +51,13 @@
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<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>
|
||||
{{_('Logfile Configuration')}}
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="collapsethree" class="panel-collapse collapse">
|
||||
<div id="collapsetwo" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
<div class="form-group">
|
||||
<label for="config_log_level">{{_('Log Level')}}</label>
|
||||
@ -159,13 +86,13 @@
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<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>
|
||||
{{_('Feature Configuration')}}
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="collapsefive" class="panel-collapse collapse">
|
||||
<div id="collapsefour" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
<div class="form-group">
|
||||
<input type="checkbox" id="config_uploading" data-control="upload_settings" name="config_uploading" {% if config.config_uploading %}checked{% endif %}>
|
||||
@ -379,13 +306,13 @@
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<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>
|
||||
{{_('External binaries')}}
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="collapseeight" class="panel-collapse collapse">
|
||||
<div id="collapsefive" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
<label for="config_converterpath">{{_('Path to Calibre E-Book Converter')}}</label>
|
||||
<div class="form-group input-group">
|
||||
@ -417,18 +344,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
{% if not show_login_button %}
|
||||
<button type="submit" name="submit" class="btn btn-default">{{_('Save')}}</button>
|
||||
{% 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 %}
|
||||
<button type="button" name="submit" id="config_submit" class="btn btn-default">{{_('Save')}}</button>
|
||||
<a href="{{ url_for('admin.admin') }}" id="config_back" class="btn btn-default">{{_('Cancel')}}</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -436,15 +355,3 @@
|
||||
{% block modal %}
|
||||
{{ filechooser_modal() }}
|
||||
{% 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 @@
|
||||
{% block body %}
|
||||
<div class="discover">
|
||||
<h2>{{title}}</h2>
|
||||
<form role="form" method="POST" autocomplete="off" class="col-md-10 col-lg-6">
|
||||
<div class="panel-group">
|
||||
<form role="form" method="POST" autocomplete="off" >
|
||||
<div class="panel-group class="col-md-10 col-lg-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">
|
||||
@ -71,7 +71,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">
|
||||
@ -146,6 +145,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<button type="submit" name="submit" class="btn btn-default">{{_('Save')}}</button>
|
||||
<a href="{{ url_for('admin.admin') }}" class="btn btn-default">{{_('Cancel')}}</a>
|
||||
@ -157,13 +157,6 @@
|
||||
{{ restrict_modal() }}
|
||||
{% endblock %}
|
||||
{% 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-editable.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-editable.min.js') }}"></script>
|
||||
|
@ -111,6 +111,7 @@
|
||||
</div>
|
||||
{%endif%}
|
||||
{% endfor %}
|
||||
{% block flash %}{% endblock %}
|
||||
{% if g.current_theme == 1 %}
|
||||
<div id="loader" hidden="true">
|
||||
<center>
|
||||
|
@ -188,7 +188,7 @@ class User(UserBase, Base):
|
||||
allowed_column_value = Column(String, default="")
|
||||
remote_auth_token = relationship('RemoteAuthToken', backref='user', lazy='dynamic')
|
||||
view_settings = Column(JSON, default={})
|
||||
kobo_only_shelves_sync = Column(Integer, default=1)
|
||||
kobo_only_shelves_sync = Column(Integer, default=0)
|
||||
|
||||
|
||||
if oauth_support:
|
||||
|
20
cps/web.py
20
cps/web.py
@ -1381,10 +1381,14 @@ def serve_book(book_id, book_format, anyname):
|
||||
return "File not in Database"
|
||||
log.info('Serving book: %s', data.name)
|
||||
if config.config_use_google_drive:
|
||||
headers = Headers()
|
||||
headers["Content-Type"] = mimetypes.types_map.get('.' + book_format, "application/octet-stream")
|
||||
df = getFileFromEbooksFolder(book.path, data.name + "." + book_format)
|
||||
return do_gdrive_download(df, headers, (book_format.upper() == 'TXT'))
|
||||
try:
|
||||
headers = Headers()
|
||||
headers["Content-Type"] = mimetypes.types_map.get('.' + book_format, "application/octet-stream")
|
||||
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:
|
||||
if book_format.upper() == 'TXT':
|
||||
try:
|
||||
@ -1394,11 +1398,11 @@ def serve_book(book_id, book_format, anyname):
|
||||
return make_response(
|
||||
rawdata.decode(result['encoding']).encode('utf-8'))
|
||||
except FileNotFoundError:
|
||||
log.error("File Not Found")
|
||||
return "File Not Found"
|
||||
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>/<anyname>")
|
||||
@login_required_if_no_ano
|
||||
@ -1489,9 +1493,9 @@ def register():
|
||||
|
||||
@web.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if not config.db_configured:
|
||||
log.debug(u"Redirect to initial configuration")
|
||||
return redirect(url_for('admin.basic_configuration'))
|
||||
#if not config.db_configured:
|
||||
# log.debug(u"Redirect to initial configuration")
|
||||
# return redirect(url_for('admin.basic_configuration'))
|
||||
if current_user is not None and current_user.is_authenticated:
|
||||
return redirect(url_for('web.index'))
|
||||
if config.config_login_type == constants.LOGIN_LDAP and not services.ldap:
|
||||
|
@ -3,7 +3,7 @@ Flask-Babel>=0.11.1,<2.1.0
|
||||
Flask-Login>=0.3.2,<0.5.1
|
||||
Flask-Principal>=0.3.2,<0.5.1
|
||||
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
|
||||
PyPDF3>=1.0.0,<1.0.4
|
||||
pytz>=2016.10
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user