mirror of
https://github.com/janeczku/calibre-web
synced 2024-11-28 12:30:00 +00:00
Merge remote-tracking branch 'github/config_sql' into Develop
This commit is contained in:
commit
e734bb120a
@ -25,10 +25,6 @@ from __future__ import division, print_function, unicode_literals
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import mimetypes
|
import mimetypes
|
||||||
try:
|
|
||||||
import cPickle
|
|
||||||
except ImportError:
|
|
||||||
import pickle as cPickle
|
|
||||||
|
|
||||||
from babel import Locale as LC
|
from babel import Locale as LC
|
||||||
from babel import negotiate_locale
|
from babel import negotiate_locale
|
||||||
@ -38,8 +34,7 @@ from flask_login import LoginManager
|
|||||||
from flask_babel import Babel
|
from flask_babel import Babel
|
||||||
from flask_principal import Principal
|
from flask_principal import Principal
|
||||||
|
|
||||||
from . import logger, cache_buster, ub
|
from . import logger, cache_buster, cli, config_sql, ub
|
||||||
from .constants import TRANSLATIONS_DIR as _TRANSLATIONS_DIR
|
|
||||||
from .reverseproxy import ReverseProxied
|
from .reverseproxy import ReverseProxied
|
||||||
|
|
||||||
|
|
||||||
@ -68,16 +63,9 @@ lm.login_view = 'web.login'
|
|||||||
lm.anonymous_user = ub.Anonymous
|
lm.anonymous_user = ub.Anonymous
|
||||||
|
|
||||||
|
|
||||||
ub.init_db()
|
ub.init_db(cli.settingspath)
|
||||||
config = ub.Config()
|
config = config_sql.load_configuration(ub.session)
|
||||||
from . import db
|
from . import db, services
|
||||||
|
|
||||||
try:
|
|
||||||
with open(os.path.join(_TRANSLATIONS_DIR, 'iso639.pickle'), 'rb') as f:
|
|
||||||
language_table = cPickle.load(f)
|
|
||||||
except cPickle.UnpicklingError as error:
|
|
||||||
print("Can't read file cps/translations/iso639.pickle: %s" % error)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
searched_ids = {}
|
searched_ids = {}
|
||||||
|
|
||||||
@ -87,10 +75,8 @@ global_WorkerThread = WorkerThread()
|
|||||||
from .server import WebServer
|
from .server import WebServer
|
||||||
web_server = WebServer()
|
web_server = WebServer()
|
||||||
|
|
||||||
from .ldap_login import Ldap
|
|
||||||
ldap1 = Ldap()
|
|
||||||
|
|
||||||
babel = Babel()
|
babel = Babel()
|
||||||
|
_BABEL_TRANSLATIONS = set()
|
||||||
|
|
||||||
log = logger.create()
|
log = logger.create()
|
||||||
|
|
||||||
@ -109,30 +95,45 @@ def create_app():
|
|||||||
Principal(app)
|
Principal(app)
|
||||||
lm.init_app(app)
|
lm.init_app(app)
|
||||||
app.secret_key = os.getenv('SECRET_KEY', 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT')
|
app.secret_key = os.getenv('SECRET_KEY', 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT')
|
||||||
|
|
||||||
web_server.init_app(app, config)
|
web_server.init_app(app, config)
|
||||||
db.setup_db()
|
db.setup_db(config)
|
||||||
|
|
||||||
babel.init_app(app)
|
babel.init_app(app)
|
||||||
ldap1.init_app(app)
|
_BABEL_TRANSLATIONS.update(str(item) for item in babel.list_translations())
|
||||||
|
_BABEL_TRANSLATIONS.add('en')
|
||||||
|
|
||||||
|
if services.ldap:
|
||||||
|
services.ldap.init_app(app, config)
|
||||||
|
if services.goodreads:
|
||||||
|
services.goodreads.connect(config.config_goodreads_api_key, config.config_goodreads_api_secret, config.config_use_goodreads)
|
||||||
|
|
||||||
global_WorkerThread.start()
|
global_WorkerThread.start()
|
||||||
return app
|
return app
|
||||||
|
|
||||||
@babel.localeselector
|
@babel.localeselector
|
||||||
def get_locale():
|
def negociate_locale():
|
||||||
# if a user is logged in, use the locale from the user settings
|
# if a user is logged in, use the locale from the user settings
|
||||||
user = getattr(g, 'user', None)
|
user = getattr(g, 'user', None)
|
||||||
# user = None
|
# user = None
|
||||||
if user is not None and hasattr(user, "locale"):
|
if user is not None and hasattr(user, "locale"):
|
||||||
if user.nickname != 'Guest': # if the account is the guest account bypass the config lang settings
|
if user.nickname != 'Guest': # if the account is the guest account bypass the config lang settings
|
||||||
return user.locale
|
return user.locale
|
||||||
translations = [str(item) for item in babel.list_translations()] + ['en']
|
|
||||||
preferred = list()
|
preferred = set()
|
||||||
|
if request.accept_languages:
|
||||||
for x in request.accept_languages.values():
|
for x in request.accept_languages.values():
|
||||||
try:
|
try:
|
||||||
preferred.append(str(LC.parse(x.replace('-', '_'))))
|
preferred.add(str(LC.parse(x.replace('-', '_'))))
|
||||||
except (UnknownLocaleError, ValueError) as e:
|
except (UnknownLocaleError, ValueError) as e:
|
||||||
log.warning('Could not parse locale "%s": %s', x, e)
|
log.warning('Could not parse locale "%s": %s', x, e)
|
||||||
preferred.append('en')
|
# preferred.append('en')
|
||||||
return negotiate_locale(preferred, translations)
|
|
||||||
|
return negotiate_locale(preferred or ['en'], _BABEL_TRANSLATIONS)
|
||||||
|
|
||||||
|
|
||||||
|
def get_locale():
|
||||||
|
return request._locale
|
||||||
|
|
||||||
|
|
||||||
@babel.timezoneselector
|
@babel.timezoneselector
|
||||||
|
@ -69,8 +69,8 @@ def stats():
|
|||||||
versions['pytz'] = 'v' + pytzVersion
|
versions['pytz'] = 'v' + pytzVersion
|
||||||
|
|
||||||
versions['Requests'] = 'v' + requests.__version__
|
versions['Requests'] = 'v' + requests.__version__
|
||||||
versions['pySqlite'] = 'v' + db.engine.dialect.dbapi.version
|
versions['pySqlite'] = 'v' + db.session.bind.dialect.dbapi.version
|
||||||
versions['Sqlite'] = 'v' + db.engine.dialect.dbapi.sqlite_version
|
versions['Sqlite'] = 'v' + db.session.bind.dialect.dbapi.sqlite_version
|
||||||
versions.update(converter.versioncheck())
|
versions.update(converter.versioncheck())
|
||||||
versions.update(serverVersion)
|
versions.update(serverVersion)
|
||||||
versions['Python'] = sys.version
|
versions['Python'] = sys.version
|
||||||
|
673
cps/admin.py
673
cps/admin.py
@ -23,13 +23,14 @@
|
|||||||
|
|
||||||
from __future__ import division, print_function, unicode_literals
|
from __future__ import division, print_function, unicode_literals
|
||||||
import os
|
import os
|
||||||
|
import base64
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
try:
|
# try:
|
||||||
from imp import reload
|
# from imp import reload
|
||||||
except ImportError:
|
# except ImportError:
|
||||||
pass
|
# pass
|
||||||
|
|
||||||
from babel import Locale as LC
|
from babel import Locale as LC
|
||||||
from babel.dates import format_datetime
|
from babel.dates import format_datetime
|
||||||
@ -38,23 +39,19 @@ from flask_login import login_required, current_user, logout_user
|
|||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
from sqlalchemy import and_
|
from sqlalchemy import and_
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
|
from sqlalchemy.sql.expression import func
|
||||||
from werkzeug.security import generate_password_hash
|
from werkzeug.security import generate_password_hash
|
||||||
|
|
||||||
from . import constants, logger, ldap1
|
from . import constants, logger, helper, services
|
||||||
from . import db, ub, web_server, get_locale, config, updater_thread, babel, gdriveutils
|
from . import db, ub, web_server, get_locale, config, updater_thread, babel, gdriveutils
|
||||||
from .helper import speaking_language, check_valid_domain, check_unrar, send_test_mail, generate_random_password, \
|
from .helper import speaking_language, check_valid_domain, send_test_mail, generate_random_password, send_registration_mail
|
||||||
send_registration_mail
|
from .gdriveutils import is_gdrive_ready, gdrive_support
|
||||||
from .gdriveutils import is_gdrive_ready, gdrive_support, downloadFile, deleteDatabaseOnChange, listRootFolders
|
|
||||||
from .web import admin_required, render_title_template, before_request, unconfigured, login_required_if_no_ano
|
from .web import admin_required, render_title_template, before_request, unconfigured, login_required_if_no_ano
|
||||||
|
|
||||||
feature_support = dict()
|
feature_support = {
|
||||||
feature_support['ldap'] = ldap1.ldap_supported()
|
'ldap': bool(services.ldap),
|
||||||
|
'goodreads': bool(services.goodreads)
|
||||||
try:
|
}
|
||||||
from goodreads.client import GoodreadsClient
|
|
||||||
feature_support['goodreads'] = True
|
|
||||||
except ImportError:
|
|
||||||
feature_support['goodreads'] = False
|
|
||||||
|
|
||||||
# try:
|
# try:
|
||||||
# import rarfile
|
# import rarfile
|
||||||
@ -63,7 +60,7 @@ except ImportError:
|
|||||||
# feature_support['rar'] = False
|
# feature_support['rar'] = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from oauth_bb import oauth_check
|
from .oauth_bb import oauth_check
|
||||||
feature_support['oauth'] = True
|
feature_support['oauth'] = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
feature_support['oauth'] = False
|
feature_support['oauth'] = False
|
||||||
@ -86,12 +83,10 @@ def admin_forbidden():
|
|||||||
@admin_required
|
@admin_required
|
||||||
def shutdown():
|
def shutdown():
|
||||||
task = int(request.args.get("parameter").strip())
|
task = int(request.args.get("parameter").strip())
|
||||||
if task == 1 or task == 0: # valid commandos received
|
if task in (0, 1): # valid commandos received
|
||||||
# close all database connections
|
# close all database connections
|
||||||
db.session.close()
|
db.dispose()
|
||||||
db.engine.dispose()
|
ub.dispose()
|
||||||
ub.session.close()
|
|
||||||
ub.engine.dispose()
|
|
||||||
|
|
||||||
showtext = {}
|
showtext = {}
|
||||||
if task == 0:
|
if task == 0:
|
||||||
@ -101,12 +96,12 @@ def shutdown():
|
|||||||
# stop gevent/tornado server
|
# stop gevent/tornado server
|
||||||
web_server.stop(task == 0)
|
web_server.stop(task == 0)
|
||||||
return json.dumps(showtext)
|
return json.dumps(showtext)
|
||||||
else:
|
|
||||||
if task == 2:
|
if task == 2:
|
||||||
db.session.close()
|
log.warning("reconnecting to calibre database")
|
||||||
db.engine.dispose()
|
db.setup_db(config)
|
||||||
db.setup_db()
|
return '{}'
|
||||||
return json.dumps({})
|
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
|
|
||||||
@ -133,8 +128,8 @@ def admin():
|
|||||||
commit = version['version']
|
commit = version['version']
|
||||||
|
|
||||||
allUser = ub.session.query(ub.User).all()
|
allUser = ub.session.query(ub.User).all()
|
||||||
settings = ub.session.query(ub.Settings).first()
|
email_settings = config.get_mail_settings()
|
||||||
return render_title_template("admin.html", allUser=allUser, email=settings, config=config, commit=commit,
|
return render_title_template("admin.html", allUser=allUser, email=email_settings, config=config, commit=commit,
|
||||||
title=_(u"Admin page"), page="admin")
|
title=_(u"Admin page"), page="admin")
|
||||||
|
|
||||||
|
|
||||||
@ -142,84 +137,60 @@ def admin():
|
|||||||
@login_required
|
@login_required
|
||||||
@admin_required
|
@admin_required
|
||||||
def configuration():
|
def configuration():
|
||||||
return configuration_helper(0)
|
if request.method == "POST":
|
||||||
|
return _configuration_update_helper()
|
||||||
|
return _configuration_result()
|
||||||
|
|
||||||
|
|
||||||
@admi.route("/admin/viewconfig", methods=["GET", "POST"])
|
@admi.route("/admin/viewconfig")
|
||||||
@login_required
|
@login_required
|
||||||
@admin_required
|
@admin_required
|
||||||
def view_configuration():
|
def view_configuration():
|
||||||
reboot_required = False
|
|
||||||
if request.method == "POST":
|
|
||||||
to_save = request.form.to_dict()
|
|
||||||
content = ub.session.query(ub.Settings).first()
|
|
||||||
if "config_calibre_web_title" in to_save:
|
|
||||||
content.config_calibre_web_title = to_save["config_calibre_web_title"]
|
|
||||||
if "config_columns_to_ignore" in to_save:
|
|
||||||
content.config_columns_to_ignore = to_save["config_columns_to_ignore"]
|
|
||||||
if "config_read_column" in to_save:
|
|
||||||
content.config_read_column = int(to_save["config_read_column"])
|
|
||||||
if "config_theme" in to_save:
|
|
||||||
content.config_theme = int(to_save["config_theme"])
|
|
||||||
if "config_title_regex" in to_save:
|
|
||||||
if content.config_title_regex != to_save["config_title_regex"]:
|
|
||||||
content.config_title_regex = to_save["config_title_regex"]
|
|
||||||
reboot_required = True
|
|
||||||
if "config_random_books" in to_save:
|
|
||||||
content.config_random_books = int(to_save["config_random_books"])
|
|
||||||
if "config_books_per_page" in to_save:
|
|
||||||
content.config_books_per_page = int(to_save["config_books_per_page"])
|
|
||||||
# Mature Content configuration
|
|
||||||
if "config_mature_content_tags" in to_save:
|
|
||||||
content.config_mature_content_tags = to_save["config_mature_content_tags"].strip()
|
|
||||||
if "Show_mature_content" in to_save:
|
|
||||||
content.config_default_show |= constants.MATURE_CONTENT
|
|
||||||
|
|
||||||
if "config_authors_max" in to_save:
|
|
||||||
content.config_authors_max = int(to_save["config_authors_max"])
|
|
||||||
|
|
||||||
# Default user configuration
|
|
||||||
content.config_default_role = 0
|
|
||||||
if "admin_role" in to_save:
|
|
||||||
content.config_default_role |= constants.ROLE_ADMIN
|
|
||||||
if "download_role" in to_save:
|
|
||||||
content.config_default_role |= constants.ROLE_DOWNLOAD
|
|
||||||
if "viewer_role" in to_save:
|
|
||||||
content.config_default_role |= constants.ROLE_VIEWER
|
|
||||||
if "upload_role" in to_save:
|
|
||||||
content.config_default_role |= constants.ROLE_UPLOAD
|
|
||||||
if "edit_role" in to_save:
|
|
||||||
content.config_default_role |= constants.ROLE_EDIT
|
|
||||||
if "delete_role" in to_save:
|
|
||||||
content.config_default_role |= constants.ROLE_DELETE_BOOKS
|
|
||||||
if "passwd_role" in to_save:
|
|
||||||
content.config_default_role |= constants.ROLE_PASSWD
|
|
||||||
if "edit_shelf_role" in to_save:
|
|
||||||
content.config_default_role |= constants.ROLE_EDIT_SHELFS
|
|
||||||
|
|
||||||
val = 0
|
|
||||||
for key, __ in to_save.items():
|
|
||||||
if key.startswith('show'):
|
|
||||||
val |= int(key[5:])
|
|
||||||
content.config_default_show = val
|
|
||||||
|
|
||||||
ub.session.commit()
|
|
||||||
flash(_(u"Calibre-Web configuration updated"), category="success")
|
|
||||||
config.loadSettings()
|
|
||||||
before_request()
|
|
||||||
if reboot_required:
|
|
||||||
# db.engine.dispose() # ToDo verify correct
|
|
||||||
# ub.session.close()
|
|
||||||
# ub.engine.dispose()
|
|
||||||
# stop Server
|
|
||||||
web_server.stop(True)
|
|
||||||
log.info('Reboot required, restarting')
|
|
||||||
readColumn = db.session.query(db.Custom_Columns)\
|
readColumn = db.session.query(db.Custom_Columns)\
|
||||||
.filter(and_(db.Custom_Columns.datatype == 'bool',db.Custom_Columns.mark_for_delete == 0)).all()
|
.filter(and_(db.Custom_Columns.datatype == 'bool',db.Custom_Columns.mark_for_delete == 0)).all()
|
||||||
return render_title_template("config_view_edit.html", conf=config, readColumns=readColumn,
|
return render_title_template("config_view_edit.html", conf=config, readColumns=readColumn,
|
||||||
title=_(u"UI Configuration"), page="uiconfig")
|
title=_(u"UI Configuration"), page="uiconfig")
|
||||||
|
|
||||||
|
|
||||||
|
@admi.route("/admin/viewconfig", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
@admin_required
|
||||||
|
def update_view_configuration():
|
||||||
|
reboot_required = False
|
||||||
|
to_save = request.form.to_dict()
|
||||||
|
|
||||||
|
_config_string = lambda x: config.set_from_dictionary(to_save, x, lambda y: y.strip() if y else y)
|
||||||
|
_config_int = lambda x: config.set_from_dictionary(to_save, x, int)
|
||||||
|
|
||||||
|
_config_string("config_calibre_web_title")
|
||||||
|
_config_string("config_columns_to_ignore")
|
||||||
|
_config_string("config_mature_content_tags")
|
||||||
|
reboot_required |= _config_string("config_title_regex")
|
||||||
|
|
||||||
|
_config_int("config_read_column")
|
||||||
|
_config_int("config_theme")
|
||||||
|
_config_int("config_random_books")
|
||||||
|
_config_int("config_books_per_page")
|
||||||
|
_config_int("config_authors_max")
|
||||||
|
|
||||||
|
config.config_default_role = constants.selected_roles(to_save)
|
||||||
|
config.config_default_role &= ~constants.ROLE_ANONYMOUS
|
||||||
|
|
||||||
|
config.config_default_show = sum(int(k[5:]) for k in to_save if k.startswith('show_'))
|
||||||
|
if "Show_mature_content" in to_save:
|
||||||
|
config.config_default_show |= constants.MATURE_CONTENT
|
||||||
|
|
||||||
|
config.save()
|
||||||
|
flash(_(u"Calibre-Web configuration updated"), category="success")
|
||||||
|
before_request()
|
||||||
|
if reboot_required:
|
||||||
|
db.dispose()
|
||||||
|
ub.dispose()
|
||||||
|
web_server.stop(True)
|
||||||
|
|
||||||
|
return view_configuration()
|
||||||
|
|
||||||
|
|
||||||
@admi.route("/ajax/editdomain", methods=['POST'])
|
@admi.route("/ajax/editdomain", methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
@admin_required
|
@admin_required
|
||||||
@ -280,281 +251,172 @@ def list_domain():
|
|||||||
@unconfigured
|
@unconfigured
|
||||||
def basic_configuration():
|
def basic_configuration():
|
||||||
logout_user()
|
logout_user()
|
||||||
return configuration_helper(1)
|
|
||||||
|
|
||||||
|
|
||||||
def configuration_helper(origin):
|
|
||||||
reboot_required = False
|
|
||||||
gdriveError = None
|
|
||||||
db_change = False
|
|
||||||
success = False
|
|
||||||
filedata = None
|
|
||||||
if not feature_support['gdrive']:
|
|
||||||
gdriveError = _('Import of optional Google Drive requirements missing')
|
|
||||||
else:
|
|
||||||
if not os.path.isfile(gdriveutils.CLIENT_SECRETS):
|
|
||||||
gdriveError = _('client_secrets.json is missing or not readable')
|
|
||||||
else:
|
|
||||||
with open(gdriveutils.CLIENT_SECRETS, 'r') as settings:
|
|
||||||
filedata = json.load(settings)
|
|
||||||
if 'web' not in filedata:
|
|
||||||
gdriveError = _('client_secrets.json is not configured for web application')
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
|
return _configuration_update_helper()
|
||||||
|
return _configuration_result()
|
||||||
|
|
||||||
|
|
||||||
|
def _configuration_update_helper():
|
||||||
|
reboot_required = False
|
||||||
|
db_change = False
|
||||||
to_save = request.form.to_dict()
|
to_save = request.form.to_dict()
|
||||||
content = ub.session.query(ub.Settings).first() # type: ub.Settings
|
|
||||||
if "config_calibre_dir" in to_save:
|
_config_string = lambda x: config.set_from_dictionary(to_save, x, lambda y: y.strip() if y else y)
|
||||||
if content.config_calibre_dir != to_save["config_calibre_dir"]:
|
_config_int = lambda x: config.set_from_dictionary(to_save, x, int)
|
||||||
content.config_calibre_dir = to_save["config_calibre_dir"]
|
_config_checkbox = lambda x: config.set_from_dictionary(to_save, x, lambda y: y == "on", False)
|
||||||
db_change = True
|
_config_checkbox_int = lambda x: config.set_from_dictionary(to_save, x, lambda y: 1 if (y == "on") else 0, 0)
|
||||||
|
|
||||||
|
db_change |= _config_string("config_calibre_dir")
|
||||||
|
|
||||||
# Google drive setup
|
# Google drive setup
|
||||||
if not os.path.isfile(gdriveutils.SETTINGS_YAML):
|
if not os.path.isfile(gdriveutils.SETTINGS_YAML):
|
||||||
content.config_use_google_drive = False
|
config.config_use_google_drive = False
|
||||||
if "config_use_google_drive" in to_save and not content.config_use_google_drive and not gdriveError:
|
|
||||||
if filedata:
|
gdrive_secrets = {}
|
||||||
if filedata['web']['redirect_uris'][0].endswith('/'):
|
gdriveError = gdriveutils.get_error_text(gdrive_secrets)
|
||||||
filedata['web']['redirect_uris'][0] = filedata['web']['redirect_uris'][0][:-1]
|
if "config_use_google_drive" in to_save and not config.config_use_google_drive and not gdriveError:
|
||||||
with open(gdriveutils.SETTINGS_YAML, 'w') as f:
|
if not gdrive_secrets:
|
||||||
yaml = "client_config_backend: settings\nclient_config_file: %(client_file)s\n" \
|
return _configuration_result('client_secrets.json is not configured for web application')
|
||||||
"client_config:\n" \
|
gdriveutils.update_settings(
|
||||||
" client_id: %(client_id)s\n client_secret: %(client_secret)s\n" \
|
gdrive_secrets['client_id'],
|
||||||
" redirect_uri: %(redirect_uri)s\n\nsave_credentials: True\n" \
|
gdrive_secrets['client_secret'],
|
||||||
"save_credentials_backend: file\nsave_credentials_file: %(credential)s\n\n" \
|
gdrive_secrets['redirect_uris'][0]
|
||||||
"get_refresh_token: True\n\noauth_scope:\n" \
|
)
|
||||||
" - https://www.googleapis.com/auth/drive\n"
|
|
||||||
f.write(yaml % {'client_file': gdriveutils.CLIENT_SECRETS,
|
|
||||||
'client_id': filedata['web']['client_id'],
|
|
||||||
'client_secret': filedata['web']['client_secret'],
|
|
||||||
'redirect_uri': filedata['web']['redirect_uris'][0],
|
|
||||||
'credential': gdriveutils.CREDENTIALS})
|
|
||||||
else:
|
|
||||||
flash(_(u'client_secrets.json is not configured for web application'), category="error")
|
|
||||||
return render_title_template("config_edit.html", config=config, origin=origin,
|
|
||||||
gdriveError=gdriveError,
|
|
||||||
gfeature_support=feature_support, title=_(u"Basic Configuration"),
|
|
||||||
page="config")
|
|
||||||
# always show google drive settings, but in case of error deny support
|
# always show google drive settings, but in case of error deny support
|
||||||
if "config_use_google_drive" in to_save and not gdriveError:
|
config.config_use_google_drive = (not gdriveError) and ("config_use_google_drive" in to_save)
|
||||||
content.config_use_google_drive = "config_use_google_drive" in to_save
|
if _config_string("config_google_drive_folder"):
|
||||||
else:
|
gdriveutils.deleteDatabaseOnChange()
|
||||||
content.config_use_google_drive = 0
|
|
||||||
if "config_google_drive_folder" in to_save:
|
|
||||||
if content.config_google_drive_folder != to_save["config_google_drive_folder"]:
|
|
||||||
content.config_google_drive_folder = to_save["config_google_drive_folder"]
|
|
||||||
deleteDatabaseOnChange()
|
|
||||||
|
|
||||||
if "config_port" in to_save:
|
reboot_required |= _config_int("config_port")
|
||||||
if content.config_port != int(to_save["config_port"]):
|
|
||||||
content.config_port = int(to_save["config_port"])
|
|
||||||
reboot_required = True
|
|
||||||
if "config_keyfile" in to_save:
|
|
||||||
if content.config_keyfile != to_save["config_keyfile"]:
|
|
||||||
if os.path.isfile(to_save["config_keyfile"]) or to_save["config_keyfile"] is u"":
|
|
||||||
content.config_keyfile = to_save["config_keyfile"]
|
|
||||||
reboot_required = True
|
|
||||||
else:
|
|
||||||
ub.session.commit()
|
|
||||||
flash(_(u'Keyfile location is not valid, please enter correct path'), category="error")
|
|
||||||
return render_title_template("config_edit.html", config=config, origin=origin,
|
|
||||||
gdriveError=gdriveError,
|
|
||||||
feature_support=feature_support, title=_(u"Basic Configuration"),
|
|
||||||
page="config")
|
|
||||||
if "config_certfile" in to_save:
|
|
||||||
if content.config_certfile != to_save["config_certfile"]:
|
|
||||||
if os.path.isfile(to_save["config_certfile"]) or to_save["config_certfile"] is u"":
|
|
||||||
content.config_certfile = to_save["config_certfile"]
|
|
||||||
reboot_required = True
|
|
||||||
else:
|
|
||||||
ub.session.commit()
|
|
||||||
flash(_(u'Certfile location is not valid, please enter correct path'), category="error")
|
|
||||||
return render_title_template("config_edit.html", config=config, origin=origin,
|
|
||||||
gdriveError=gdriveError, feature_support=feature_support,
|
|
||||||
title=_(u"Basic Configuration"), page="config")
|
|
||||||
content.config_uploading = 0
|
|
||||||
content.config_anonbrowse = 0
|
|
||||||
content.config_public_reg = 0
|
|
||||||
if "config_uploading" in to_save and to_save["config_uploading"] == "on":
|
|
||||||
content.config_uploading = 1
|
|
||||||
if "config_anonbrowse" in to_save and to_save["config_anonbrowse"] == "on":
|
|
||||||
content.config_anonbrowse = 1
|
|
||||||
if "config_public_reg" in to_save and to_save["config_public_reg"] == "on":
|
|
||||||
content.config_public_reg = 1
|
|
||||||
|
|
||||||
if "config_converterpath" in to_save:
|
reboot_required |= _config_string("config_keyfile")
|
||||||
content.config_converterpath = to_save["config_converterpath"].strip()
|
if config.config_keyfile and not os.path.isfile(config.config_keyfile):
|
||||||
if "config_calibre" in to_save:
|
return _configuration_result('Keyfile location is not valid, please enter correct path', gdriveError)
|
||||||
content.config_calibre = to_save["config_calibre"].strip()
|
|
||||||
if "config_ebookconverter" in to_save:
|
reboot_required |= _config_string("config_certfile")
|
||||||
content.config_ebookconverter = int(to_save["config_ebookconverter"])
|
if config.config_certfile and not os.path.isfile(config.config_certfile):
|
||||||
|
return _configuration_result('Certfile location is not valid, please enter correct path', gdriveError)
|
||||||
|
|
||||||
|
_config_checkbox_int("config_uploading")
|
||||||
|
_config_checkbox_int("config_anonbrowse")
|
||||||
|
_config_checkbox_int("config_public_reg")
|
||||||
|
|
||||||
|
_config_int("config_ebookconverter")
|
||||||
|
_config_string("config_calibre")
|
||||||
|
_config_string("config_converterpath")
|
||||||
|
|
||||||
|
if _config_int("config_login_type"):
|
||||||
|
reboot_required |= config.config_login_type != constants.LOGIN_STANDARD
|
||||||
|
|
||||||
#LDAP configurator,
|
#LDAP configurator,
|
||||||
if "config_login_type" in to_save and to_save["config_login_type"] == "1":
|
if config.config_login_type == constants.LOGIN_LDAP:
|
||||||
if not to_save["config_ldap_provider_url"] or not to_save["config_ldap_port"] or not to_save["config_ldap_dn"] or not to_save["config_ldap_user_object"]:
|
_config_string("config_ldap_provider_url")
|
||||||
ub.session.commit()
|
_config_int("config_ldap_port")
|
||||||
flash(_(u'Please enter a LDAP provider, port, DN and user object identifier'), category="error")
|
_config_string("config_ldap_schema")
|
||||||
return render_title_template("config_edit.html", content=config, origin=origin,
|
_config_string("config_ldap_dn")
|
||||||
gdrive=gdriveutils.gdrive_support, gdriveError=gdriveError,
|
_config_string("config_ldap_user_object")
|
||||||
feature_support=feature_support, title=_(u"Basic Configuration"),
|
if not config.config_ldap_provider_url or not config.config_ldap_port or not config.config_ldap_dn or not config.config_ldap_user_object:
|
||||||
page="config")
|
return _configuration_result('Please enter a LDAP provider, port, DN and user object identifier', gdriveError)
|
||||||
elif not to_save["config_ldap_serv_username"] or not to_save["config_ldap_serv_password"]:
|
|
||||||
ub.session.commit()
|
_config_string("config_ldap_serv_username")
|
||||||
flash(_(u'Please enter a LDAP service account and password'), category="error")
|
if not config.config_ldap_serv_username or "config_ldap_serv_password" not in to_save:
|
||||||
return render_title_template("config_edit.html", content=config, origin=origin,
|
return _configuration_result('Please enter a LDAP service account and password', gdriveError)
|
||||||
gdrive=gdriveutils.gdrive_support, gdriveError=gdriveError,
|
config.set_from_dictionary(to_save, "config_ldap_serv_password", base64.b64encode)
|
||||||
feature_support=feature_support, title=_(u"Basic Configuration"),
|
|
||||||
page="config")
|
_config_checkbox("config_ldap_use_ssl")
|
||||||
else:
|
_config_checkbox("config_ldap_use_tls")
|
||||||
content.config_use_ldap = 1
|
_config_checkbox("config_ldap_openldap")
|
||||||
content.config_ldap_provider_url = to_save["config_ldap_provider_url"]
|
_config_checkbox("config_ldap_require_cert")
|
||||||
content.config_ldap_port = to_save["config_ldap_port"]
|
_config_string("config_ldap_cert_path")
|
||||||
content.config_ldap_schema = to_save["config_ldap_schema"]
|
if config.config_ldap_cert_path and not os.path.isfile(config.config_ldap_cert_path):
|
||||||
content.config_ldap_serv_username = to_save["config_ldap_serv_username"]
|
return _configuration_result('LDAP Certfile location is not valid, please enter correct path', gdriveError)
|
||||||
content.config_ldap_serv_password = base64.b64encode(to_save["config_ldap_serv_password"])
|
|
||||||
content.config_ldap_dn = to_save["config_ldap_dn"]
|
|
||||||
content.config_ldap_user_object = to_save["config_ldap_user_object"]
|
|
||||||
reboot_required = True
|
|
||||||
content.config_ldap_use_ssl = 0
|
|
||||||
content.config_ldap_use_tls = 0
|
|
||||||
content.config_ldap_require_cert = 0
|
|
||||||
content.config_ldap_openldap = 0
|
|
||||||
if "config_ldap_use_ssl" in to_save and to_save["config_ldap_use_ssl"] == "on":
|
|
||||||
content.config_ldap_use_ssl = 1
|
|
||||||
if "config_ldap_use_tls" in to_save and to_save["config_ldap_use_tls"] == "on":
|
|
||||||
content.config_ldap_use_tls = 1
|
|
||||||
if "config_ldap_require_cert" in to_save and to_save["config_ldap_require_cert"] == "on":
|
|
||||||
content.config_ldap_require_cert = 1
|
|
||||||
if "config_ldap_openldap" in to_save and to_save["config_ldap_openldap"] == "on":
|
|
||||||
content.config_ldap_openldap = 1
|
|
||||||
if "config_ldap_cert_path " in to_save:
|
|
||||||
if content.config_ldap_cert_path != to_save["config_ldap_cert_path "]:
|
|
||||||
if os.path.isfile(to_save["config_ldap_cert_path "]) or to_save["config_ldap_cert_path "] is u"":
|
|
||||||
content.config_certfile = to_save["config_ldap_cert_path "]
|
|
||||||
else:
|
|
||||||
ub.session.commit()
|
|
||||||
flash(_(u'Certfile location is not valid, please enter correct path'), category="error")
|
|
||||||
return render_title_template("config_edit.html", content=config, origin=origin,
|
|
||||||
gdrive=gdriveutils.gdrive_support, gdriveError=gdriveError,
|
|
||||||
feature_support=feature_support, title=_(u"Basic Configuration"),
|
|
||||||
page="config")
|
|
||||||
|
|
||||||
# Remote login configuration
|
# Remote login configuration
|
||||||
content.config_remote_login = ("config_remote_login" in to_save and to_save["config_remote_login"] == "on")
|
_config_checkbox("config_remote_login")
|
||||||
if not content.config_remote_login:
|
if not config.config_remote_login:
|
||||||
ub.session.query(ub.RemoteAuthToken).delete()
|
ub.session.query(ub.RemoteAuthToken).delete()
|
||||||
|
|
||||||
# Goodreads configuration
|
# Goodreads configuration
|
||||||
content.config_use_goodreads = ("config_use_goodreads" in to_save and to_save["config_use_goodreads"] == "on")
|
_config_checkbox("config_use_goodreads")
|
||||||
if "config_goodreads_api_key" in to_save:
|
_config_string("config_goodreads_api_key")
|
||||||
content.config_goodreads_api_key = to_save["config_goodreads_api_key"]
|
_config_string("config_goodreads_api_secret")
|
||||||
if "config_goodreads_api_secret" in to_save:
|
if services.goodreads:
|
||||||
content.config_goodreads_api_secret = to_save["config_goodreads_api_secret"]
|
services.goodreads.connect(config.config_goodreads_api_key, config.config_goodreads_api_secret, config.config_use_goodreads)
|
||||||
if "config_updater" in to_save:
|
|
||||||
content.config_updatechannel = int(to_save["config_updater"])
|
_config_int("config_updatechannel")
|
||||||
|
|
||||||
# GitHub OAuth configuration
|
# GitHub OAuth configuration
|
||||||
if "config_login_type" in to_save and to_save["config_login_type"] == "2":
|
if config.config_login_type == constants.LOGIN_OAUTH_GITHUB:
|
||||||
if to_save["config_github_oauth_client_id"] == u'' or to_save["config_github_oauth_client_secret"] == u'':
|
_config_string("config_github_oauth_client_id")
|
||||||
ub.session.commit()
|
_config_string("config_github_oauth_client_secret")
|
||||||
flash(_(u'Please enter Github oauth credentials'), category="error")
|
if not config.config_github_oauth_client_id or not config.config_github_oauth_client_secret:
|
||||||
return render_title_template("config_edit.html", config=config, origin=origin,
|
return _configuration_result('Please enter Github oauth credentials', gdriveError)
|
||||||
gdriveError=gdriveError, feature_support=feature_support,
|
|
||||||
title=_(u"Basic Configuration"), page="config")
|
|
||||||
else:
|
|
||||||
content.config_login_type = constants.LOGIN_OAUTH_GITHUB
|
|
||||||
content.config_github_oauth_client_id = to_save["config_github_oauth_client_id"]
|
|
||||||
content.config_github_oauth_client_secret = to_save["config_github_oauth_client_secret"]
|
|
||||||
reboot_required = True
|
|
||||||
|
|
||||||
# Google OAuth configuration
|
# Google OAuth configuration
|
||||||
if "config_login_type" in to_save and to_save["config_login_type"] == "3":
|
if config.config_login_type == constants.LOGIN_OAUTH_GOOGLE:
|
||||||
if to_save["config_google_oauth_client_id"] == u'' or to_save["config_google_oauth_client_secret"] == u'':
|
_config_string("config_google_oauth_client_id")
|
||||||
ub.session.commit()
|
_config_string("config_google_oauth_client_secret")
|
||||||
flash(_(u'Please enter Google oauth credentials'), category="error")
|
if not config.config_google_oauth_client_id or not config.config_google_oauth_client_secret:
|
||||||
return render_title_template("config_edit.html", config=config, origin=origin,
|
return _configuration_result('Please enter Google oauth credentials', gdriveError)
|
||||||
gdriveError=gdriveError, feature_support=feature_support,
|
|
||||||
title=_(u"Basic Configuration"), page="config")
|
|
||||||
else:
|
|
||||||
content.config_login_type = constants.LOGIN_OAUTH_GOOGLE
|
|
||||||
content.config_google_oauth_client_id = to_save["config_google_oauth_client_id"]
|
|
||||||
content.config_google_oauth_client_secret = to_save["config_google_oauth_client_secret"]
|
|
||||||
reboot_required = True
|
|
||||||
|
|
||||||
if "config_login_type" in to_save and to_save["config_login_type"] == "0":
|
_config_int("config_log_level")
|
||||||
content.config_login_type = constants.LOGIN_STANDARD
|
_config_string("config_logfile")
|
||||||
|
if not logger.is_valid_logfile(config.config_logfile):
|
||||||
|
return _configuration_result('Logfile location is not valid, please enter correct path', gdriveError)
|
||||||
|
|
||||||
if "config_log_level" in to_save:
|
reboot_required |= _config_checkbox_int("config_access_log")
|
||||||
content.config_log_level = int(to_save["config_log_level"])
|
reboot_required |= _config_string("config_access_logfile")
|
||||||
if content.config_logfile != to_save["config_logfile"]:
|
if not logger.is_valid_logfile(config.config_access_logfile):
|
||||||
# check valid path, only path or file
|
return _configuration_result('Access Logfile location is not valid, please enter correct path', gdriveError)
|
||||||
if not logger.is_valid_logfile(to_save["config_logfile"]):
|
|
||||||
ub.session.commit()
|
|
||||||
flash(_(u'Logfile location is not valid, please enter correct path'), category="error")
|
|
||||||
return render_title_template("config_edit.html", config=config, origin=origin,
|
|
||||||
gdriveError=gdriveError, feature_support=feature_support,
|
|
||||||
title=_(u"Basic Configuration"), page="config")
|
|
||||||
content.config_logfile = to_save["config_logfile"]
|
|
||||||
|
|
||||||
content.config_access_log = 0
|
|
||||||
if "config_access_log" in to_save and to_save["config_access_log"] == "on":
|
|
||||||
content.config_access_log = 1
|
|
||||||
reboot_required = True
|
|
||||||
if "config_access_log" not in to_save and config.config_access_log:
|
|
||||||
reboot_required = True
|
|
||||||
|
|
||||||
if content.config_access_logfile != to_save["config_access_logfile"]:
|
|
||||||
# check valid path, only path or file
|
|
||||||
if not logger.is_valid_logfile(to_save["config_access_logfile"]):
|
|
||||||
ub.session.commit()
|
|
||||||
flash(_(u'Access Logfile location is not valid, please enter correct path'), category="error")
|
|
||||||
return render_title_template("config_edit.html", config=config, origin=origin,
|
|
||||||
gdriveError=gdriveError, feature_support=feature_support,
|
|
||||||
title=_(u"Basic Configuration"), page="config")
|
|
||||||
content.config_access_logfile = to_save["config_access_logfile"]
|
|
||||||
reboot_required = True
|
|
||||||
|
|
||||||
# Rarfile Content configuration
|
# Rarfile Content configuration
|
||||||
if "config_rarfile_location" in to_save and to_save['config_rarfile_location'] is not u"":
|
_config_string("config_rarfile_location")
|
||||||
check = check_unrar(to_save["config_rarfile_location"].strip())
|
unrar_status = helper.check_unrar(config.config_rarfile_location)
|
||||||
if not check[0] :
|
if unrar_status:
|
||||||
content.config_rarfile_location = to_save["config_rarfile_location"].strip()
|
return _configuration_result(unrar_status, gdriveError)
|
||||||
else:
|
|
||||||
flash(check[1], category="error")
|
|
||||||
return render_title_template("config_edit.html", config=config, origin=origin,
|
|
||||||
feature_support=feature_support, title=_(u"Basic Configuration"))
|
|
||||||
try:
|
try:
|
||||||
if content.config_use_google_drive and is_gdrive_ready() and not \
|
metadata_db = os.path.join(config.config_calibre_dir, "metadata.db")
|
||||||
os.path.exists(os.path.join(content.config_calibre_dir, "metadata.db")):
|
if config.config_use_google_drive and is_gdrive_ready() and not os.path.exists(metadata_db):
|
||||||
downloadFile(None, "metadata.db", config.config_calibre_dir + "/metadata.db")
|
gdriveutils.downloadFile(None, "metadata.db", metadata_db)
|
||||||
if db_change:
|
db_change = True
|
||||||
if config.db_configured:
|
|
||||||
db.session.close()
|
|
||||||
db.engine.dispose()
|
|
||||||
ub.session.commit()
|
|
||||||
flash(_(u"Calibre-Web configuration updated"), category="success")
|
|
||||||
config.loadSettings()
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
flash(e, category="error")
|
return _configuration_result('%s' % e, gdriveError)
|
||||||
return render_title_template("config_edit.html", config=config, origin=origin,
|
|
||||||
gdriveError=gdriveError, feature_support=feature_support,
|
|
||||||
title=_(u"Basic Configuration"), page="config")
|
|
||||||
if db_change:
|
if db_change:
|
||||||
reload(db)
|
# reload(db)
|
||||||
if not db.setup_db():
|
if not db.setup_db(config):
|
||||||
flash(_(u'DB location is not valid, please enter correct path'), category="error")
|
return _configuration_result('DB location is not valid, please enter correct path', gdriveError)
|
||||||
return render_title_template("config_edit.html", config=config, origin=origin,
|
|
||||||
gdriveError=gdriveError, feature_support=feature_support,
|
config.save()
|
||||||
title=_(u"Basic Configuration"), page="config")
|
flash(_(u"Calibre-Web configuration updated"), category="success")
|
||||||
if reboot_required:
|
if reboot_required:
|
||||||
# stop Server
|
|
||||||
web_server.stop(True)
|
web_server.stop(True)
|
||||||
log.info('Reboot required, restarting')
|
|
||||||
if origin:
|
return _configuration_result(None, gdriveError)
|
||||||
success = True
|
|
||||||
if is_gdrive_ready() and feature_support['gdrive'] is True and config.config_use_google_drive == True:
|
|
||||||
gdrivefolders = listRootFolders()
|
def _configuration_result(error_flash=None, gdriveError=None):
|
||||||
|
gdrive_authenticate = not is_gdrive_ready()
|
||||||
|
gdrivefolders = []
|
||||||
|
if gdriveError is None:
|
||||||
|
gdriveError = gdriveutils.get_error_text()
|
||||||
|
if gdriveError:
|
||||||
|
gdriveError = _(gdriveError)
|
||||||
else:
|
else:
|
||||||
gdrivefolders = list()
|
gdrivefolders = gdriveutils.listRootFolders()
|
||||||
return render_title_template("config_edit.html", origin=origin, success=success, config=config,
|
|
||||||
show_authenticate_google_drive=not is_gdrive_ready(),
|
show_back_button = current_user.is_authenticated
|
||||||
|
show_login_button = config.db_configured and not current_user.is_authenticated
|
||||||
|
if error_flash:
|
||||||
|
config.load()
|
||||||
|
flash(_(error_flash), category="error")
|
||||||
|
show_login_button = False
|
||||||
|
|
||||||
|
return render_title_template("config_edit.html", config=config,
|
||||||
|
show_back_button=show_back_button, show_login_button=show_login_button,
|
||||||
|
show_authenticate_google_drive=gdrive_authenticate,
|
||||||
gdriveError=gdriveError, gdrivefolders=gdrivefolders, feature_support=feature_support,
|
gdriveError=gdriveError, gdrivefolders=gdrivefolders, feature_support=feature_support,
|
||||||
title=_(u"Basic Configuration"), page="config")
|
title=_(u"Basic Configuration"), page="config")
|
||||||
|
|
||||||
@ -570,34 +432,14 @@ def new_user():
|
|||||||
to_save = request.form.to_dict()
|
to_save = request.form.to_dict()
|
||||||
content.default_language = to_save["default_language"]
|
content.default_language = to_save["default_language"]
|
||||||
content.mature_content = "Show_mature_content" in to_save
|
content.mature_content = "Show_mature_content" in to_save
|
||||||
if "locale" in to_save:
|
content.locale = to_save.get("locale", content.locale)
|
||||||
content.locale = to_save["locale"]
|
|
||||||
|
|
||||||
val = 0
|
|
||||||
for key, __ in to_save.items():
|
|
||||||
if key.startswith('show'):
|
|
||||||
val += int(key[5:])
|
|
||||||
content.sidebar_view = val
|
|
||||||
|
|
||||||
|
|
||||||
|
content.sidebar_view = sum(int(key[5:]) for key in to_save if key.startswith('show_'))
|
||||||
if "show_detail_random" in to_save:
|
if "show_detail_random" in to_save:
|
||||||
content.sidebar_view |= constants.DETAIL_RANDOM
|
content.sidebar_view |= constants.DETAIL_RANDOM
|
||||||
|
|
||||||
content.role = 0
|
content.role = constants.selected_roles(to_save)
|
||||||
if "admin_role" in to_save:
|
|
||||||
content.role |= constants.ROLE_ADMIN
|
|
||||||
if "download_role" in to_save:
|
|
||||||
content.role |= constants.ROLE_DOWNLOAD
|
|
||||||
if "upload_role" in to_save:
|
|
||||||
content.role |= constants.ROLE_UPLOAD
|
|
||||||
if "edit_role" in to_save:
|
|
||||||
content.role |= constants.ROLE_EDIT
|
|
||||||
if "delete_role" in to_save:
|
|
||||||
content.role |= constants.ROLE_DELETE_BOOKS
|
|
||||||
if "passwd_role" in to_save:
|
|
||||||
content.role |= constants.ROLE_PASSWD
|
|
||||||
if "edit_shelf_role" in to_save:
|
|
||||||
content.role |= constants.ROLE_EDIT_SHELFS
|
|
||||||
if not to_save["nickname"] or not to_save["email"] or not to_save["password"]:
|
if not to_save["nickname"] or not to_save["email"] or not to_save["password"]:
|
||||||
flash(_(u"Please fill out all fields!"), category="error")
|
flash(_(u"Please fill out all fields!"), category="error")
|
||||||
return render_title_template("user_edit.html", new_user=1, content=content, translations=translations,
|
return render_title_template("user_edit.html", new_user=1, content=content, translations=translations,
|
||||||
@ -637,24 +479,35 @@ def new_user():
|
|||||||
registered_oauth=oauth_check)
|
registered_oauth=oauth_check)
|
||||||
|
|
||||||
|
|
||||||
@admi.route("/admin/mailsettings", methods=["GET", "POST"])
|
@admi.route("/admin/mailsettings")
|
||||||
@login_required
|
@login_required
|
||||||
@admin_required
|
@admin_required
|
||||||
def edit_mailsettings():
|
def edit_mailsettings():
|
||||||
content = ub.session.query(ub.Settings).first()
|
content = config.get_mail_settings()
|
||||||
if request.method == "POST":
|
# log.debug("edit_mailsettings %r", content)
|
||||||
|
return render_title_template("email_edit.html", content=content, title=_(u"Edit e-mail server settings"),
|
||||||
|
page="mailset")
|
||||||
|
|
||||||
|
|
||||||
|
@admi.route("/admin/mailsettings", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
@admin_required
|
||||||
|
def update_mailsettings():
|
||||||
to_save = request.form.to_dict()
|
to_save = request.form.to_dict()
|
||||||
content.mail_server = to_save["mail_server"]
|
log.debug("update_mailsettings %r", to_save)
|
||||||
content.mail_port = int(to_save["mail_port"])
|
|
||||||
content.mail_login = to_save["mail_login"]
|
_config_string = lambda x: config.set_from_dictionary(to_save, x, lambda y: y.strip() if y else y)
|
||||||
content.mail_password = to_save["mail_password"]
|
_config_int = lambda x: config.set_from_dictionary(to_save, x, int)
|
||||||
content.mail_from = to_save["mail_from"]
|
|
||||||
content.mail_use_ssl = int(to_save["mail_use_ssl"])
|
_config_string("mail_server")
|
||||||
try:
|
_config_int("mail_port")
|
||||||
ub.session.commit()
|
_config_int("mail_use_ssl")
|
||||||
except Exception as e:
|
_config_string("mail_login")
|
||||||
flash(e, category="error")
|
_config_string("mail_password")
|
||||||
if "test" in to_save and to_save["test"]:
|
_config_string("mail_from")
|
||||||
|
config.save()
|
||||||
|
|
||||||
|
if to_save.get("test"):
|
||||||
if current_user.kindle_mail:
|
if current_user.kindle_mail:
|
||||||
result = send_test_mail(current_user.kindle_mail, current_user.nickname)
|
result = send_test_mail(current_user.kindle_mail, current_user.nickname)
|
||||||
if result is None:
|
if result is None:
|
||||||
@ -666,8 +519,8 @@ def edit_mailsettings():
|
|||||||
flash(_(u"Please configure your kindle e-mail address first..."), category="error")
|
flash(_(u"Please configure your kindle e-mail address first..."), category="error")
|
||||||
else:
|
else:
|
||||||
flash(_(u"E-mail server settings updated"), category="success")
|
flash(_(u"E-mail server settings updated"), category="success")
|
||||||
return render_title_template("email_edit.html", content=content, title=_(u"Edit e-mail server settings"),
|
|
||||||
page="mailset")
|
return edit_mailsettings()
|
||||||
|
|
||||||
|
|
||||||
@admi.route("/admin/user/<int:user_id>", methods=["GET", "POST"])
|
@admi.route("/admin/user/<int:user_id>", methods=["GET", "POST"])
|
||||||
@ -703,53 +556,21 @@ def edit_user(user_id):
|
|||||||
if "password" in to_save and to_save["password"]:
|
if "password" in to_save and to_save["password"]:
|
||||||
content.password = generate_password_hash(to_save["password"])
|
content.password = generate_password_hash(to_save["password"])
|
||||||
|
|
||||||
if "admin_role" in to_save:
|
anonymous = content.is_anonymous
|
||||||
content.role |= constants.ROLE_ADMIN
|
content.role = constants.selected_roles(to_save)
|
||||||
|
if anonymous:
|
||||||
|
content.role |= constants.ROLE_ANONYMOUS
|
||||||
else:
|
else:
|
||||||
content.role &= ~constants.ROLE_ADMIN
|
content.role &= ~constants.ROLE_ANONYMOUS
|
||||||
|
|
||||||
if "download_role" in to_save:
|
val = [int(k[5:]) for k in to_save if k.startswith('show_')]
|
||||||
content.role |= constants.ROLE_DOWNLOAD
|
|
||||||
else:
|
|
||||||
content.role &= ~constants.ROLE_DOWNLOAD
|
|
||||||
|
|
||||||
if "viewer_role" in to_save:
|
|
||||||
content.role |= constants.ROLE_VIEWER
|
|
||||||
else:
|
|
||||||
content.role &= ~constants.ROLE_VIEWER
|
|
||||||
|
|
||||||
if "upload_role" in to_save:
|
|
||||||
content.role |= constants.ROLE_UPLOAD
|
|
||||||
else:
|
|
||||||
content.role &= ~constants.ROLE_UPLOAD
|
|
||||||
|
|
||||||
if "edit_role" in to_save:
|
|
||||||
content.role |= constants.ROLE_EDIT
|
|
||||||
else:
|
|
||||||
content.role &= ~constants.ROLE_EDIT
|
|
||||||
|
|
||||||
if "delete_role" in to_save:
|
|
||||||
content.role |= constants.ROLE_DELETE_BOOKS
|
|
||||||
else:
|
|
||||||
content.role &= ~constants.ROLE_DELETE_BOOKS
|
|
||||||
|
|
||||||
if "passwd_role" in to_save:
|
|
||||||
content.role |= constants.ROLE_PASSWD
|
|
||||||
else:
|
|
||||||
content.role &= ~constants.ROLE_PASSWD
|
|
||||||
|
|
||||||
if "edit_shelf_role" in to_save:
|
|
||||||
content.role |= constants.ROLE_EDIT_SHELFS
|
|
||||||
else:
|
|
||||||
content.role &= ~constants.ROLE_EDIT_SHELFS
|
|
||||||
|
|
||||||
val = [int(k[5:]) for k, __ in to_save.items() if k.startswith('show_')]
|
|
||||||
sidebar = ub.get_sidebar_config()
|
sidebar = ub.get_sidebar_config()
|
||||||
for element in sidebar:
|
for element in sidebar:
|
||||||
if element['visibility'] in val and not content.check_visibility(element['visibility']):
|
value = element['visibility']
|
||||||
content.sidebar_view |= element['visibility']
|
if value in val and not content.check_visibility(value):
|
||||||
elif not element['visibility'] in val and content.check_visibility(element['visibility']):
|
content.sidebar_view |= value
|
||||||
content.sidebar_view &= ~element['visibility']
|
elif not value in val and content.check_visibility(value):
|
||||||
|
content.sidebar_view &= ~value
|
||||||
|
|
||||||
if "Show_detail_random" in to_save:
|
if "Show_detail_random" in to_save:
|
||||||
content.sidebar_view |= constants.DETAIL_RANDOM
|
content.sidebar_view |= constants.DETAIL_RANDOM
|
||||||
|
287
cps/config_sql.py
Normal file
287
cps/config_sql.py
Normal file
@ -0,0 +1,287 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
|
||||||
|
# Copyright (C) 2019 OzzieIsaacs, pwr
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
from __future__ import division, print_function, unicode_literals
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
|
||||||
|
from sqlalchemy import exc, Column, String, Integer, SmallInteger, Boolean
|
||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
|
||||||
|
from . import constants, cli, logger
|
||||||
|
|
||||||
|
|
||||||
|
log = logger.create()
|
||||||
|
_Base = declarative_base()
|
||||||
|
|
||||||
|
|
||||||
|
# Baseclass for representing settings in app.db with email server settings and Calibre database settings
|
||||||
|
# (application settings)
|
||||||
|
class _Settings(_Base):
|
||||||
|
__tablename__ = 'settings'
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
mail_server = Column(String, default='mail.example.org')
|
||||||
|
mail_port = Column(Integer, default=25)
|
||||||
|
mail_use_ssl = Column(SmallInteger, default=0)
|
||||||
|
mail_login = Column(String, default='mail@example.com')
|
||||||
|
mail_password = Column(String, default='mypassword')
|
||||||
|
mail_from = Column(String, default='automailer <mail@example.com>')
|
||||||
|
config_calibre_dir = Column(String)
|
||||||
|
config_port = Column(Integer, default=constants.DEFAULT_PORT)
|
||||||
|
config_certfile = Column(String)
|
||||||
|
config_keyfile = Column(String)
|
||||||
|
config_calibre_web_title = Column(String, default=u'Calibre-Web')
|
||||||
|
config_books_per_page = Column(Integer, default=60)
|
||||||
|
config_random_books = Column(Integer, default=4)
|
||||||
|
config_authors_max = Column(Integer, default=0)
|
||||||
|
config_read_column = Column(Integer, default=0)
|
||||||
|
config_title_regex = Column(String, default=u'^(A|The|An|Der|Die|Das|Den|Ein|Eine|Einen|Dem|Des|Einem|Eines)\s+')
|
||||||
|
config_log_level = Column(SmallInteger, default=logger.DEFAULT_LOG_LEVEL)
|
||||||
|
config_access_log = Column(SmallInteger, default=0)
|
||||||
|
config_uploading = Column(SmallInteger, default=0)
|
||||||
|
config_anonbrowse = Column(SmallInteger, default=0)
|
||||||
|
config_public_reg = Column(SmallInteger, default=0)
|
||||||
|
config_default_role = Column(SmallInteger, default=0)
|
||||||
|
config_default_show = Column(SmallInteger, default=6143)
|
||||||
|
config_columns_to_ignore = Column(String)
|
||||||
|
config_use_google_drive = Column(Boolean, default=False)
|
||||||
|
config_google_drive_folder = Column(String)
|
||||||
|
config_google_drive_watch_changes_response = Column(String)
|
||||||
|
config_remote_login = Column(Boolean, default=False)
|
||||||
|
config_use_goodreads = Column(Boolean, default=False)
|
||||||
|
config_goodreads_api_key = Column(String)
|
||||||
|
config_goodreads_api_secret = Column(String)
|
||||||
|
config_login_type = Column(Integer, default=0)
|
||||||
|
# config_use_ldap = Column(Boolean)
|
||||||
|
config_ldap_provider_url = Column(String)
|
||||||
|
config_ldap_dn = Column(String)
|
||||||
|
# config_use_github_oauth = Column(Boolean)
|
||||||
|
config_github_oauth_client_id = Column(String)
|
||||||
|
config_github_oauth_client_secret = Column(String)
|
||||||
|
# config_use_google_oauth = Column(Boolean)
|
||||||
|
config_google_oauth_client_id = Column(String)
|
||||||
|
config_google_oauth_client_secret = Column(String)
|
||||||
|
config_ldap_provider_url = Column(String, default='localhost')
|
||||||
|
config_ldap_port = Column(SmallInteger, default=389)
|
||||||
|
config_ldap_schema = Column(String, default='ldap')
|
||||||
|
config_ldap_serv_username = Column(String)
|
||||||
|
config_ldap_serv_password = Column(String)
|
||||||
|
config_ldap_use_ssl = Column(Boolean, default=False)
|
||||||
|
config_ldap_use_tls = Column(Boolean, default=False)
|
||||||
|
config_ldap_require_cert = Column(Boolean, default=False)
|
||||||
|
config_ldap_cert_path = Column(String)
|
||||||
|
config_ldap_dn = Column(String)
|
||||||
|
config_ldap_user_object = Column(String)
|
||||||
|
config_ldap_openldap = Column(Boolean, default=False)
|
||||||
|
config_mature_content_tags = Column(String, default='')
|
||||||
|
config_logfile = Column(String)
|
||||||
|
config_access_logfile = Column(String)
|
||||||
|
config_ebookconverter = Column(Integer, default=0)
|
||||||
|
config_converterpath = Column(String)
|
||||||
|
config_calibre = Column(String)
|
||||||
|
config_rarfile_location = Column(String)
|
||||||
|
config_theme = Column(Integer, default=0)
|
||||||
|
config_updatechannel = Column(Integer, default=constants.UPDATE_STABLE)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.__class__.__name__
|
||||||
|
|
||||||
|
|
||||||
|
# Class holds all application specific settings in calibre-web
|
||||||
|
class _ConfigSQL(object):
|
||||||
|
# pylint: disable=no-member
|
||||||
|
def __init__(self, session):
|
||||||
|
self._session = session
|
||||||
|
self._settings = None
|
||||||
|
self.db_configured = None
|
||||||
|
self.config_calibre_dir = None
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def _read_from_storage(self):
|
||||||
|
if self._settings is None:
|
||||||
|
log.debug("_ConfigSQL._read_from_storage")
|
||||||
|
self._settings = self._session.query(_Settings).first()
|
||||||
|
return self._settings
|
||||||
|
|
||||||
|
def get_config_certfile(self):
|
||||||
|
if cli.certfilepath:
|
||||||
|
return cli.certfilepath
|
||||||
|
if cli.certfilepath == "":
|
||||||
|
return None
|
||||||
|
return self.config_certfile
|
||||||
|
|
||||||
|
def get_config_keyfile(self):
|
||||||
|
if cli.keyfilepath:
|
||||||
|
return cli.keyfilepath
|
||||||
|
if cli.certfilepath == "":
|
||||||
|
return None
|
||||||
|
return self.config_keyfile
|
||||||
|
|
||||||
|
def get_config_ipaddress(self):
|
||||||
|
return cli.ipadress or ""
|
||||||
|
|
||||||
|
def get_ipaddress_type(self):
|
||||||
|
return cli.ipv6
|
||||||
|
|
||||||
|
def _has_role(self, role_flag):
|
||||||
|
return constants.has_flag(self.config_default_role, role_flag)
|
||||||
|
|
||||||
|
def role_admin(self):
|
||||||
|
return self._has_role(constants.ROLE_ADMIN)
|
||||||
|
|
||||||
|
def role_download(self):
|
||||||
|
return self._has_role(constants.ROLE_DOWNLOAD)
|
||||||
|
|
||||||
|
def role_viewer(self):
|
||||||
|
return self._has_role(constants.ROLE_VIEWER)
|
||||||
|
|
||||||
|
def role_upload(self):
|
||||||
|
return self._has_role(constants.ROLE_UPLOAD)
|
||||||
|
|
||||||
|
def role_edit(self):
|
||||||
|
return self._has_role(constants.ROLE_EDIT)
|
||||||
|
|
||||||
|
def role_passwd(self):
|
||||||
|
return self._has_role(constants.ROLE_PASSWD)
|
||||||
|
|
||||||
|
def role_edit_shelfs(self):
|
||||||
|
return self._has_role(constants.ROLE_EDIT_SHELFS)
|
||||||
|
|
||||||
|
def role_delete_books(self):
|
||||||
|
return self._has_role(constants.ROLE_DELETE_BOOKS)
|
||||||
|
|
||||||
|
def show_element_new_user(self, value):
|
||||||
|
return constants.has_flag(self.config_default_show, value)
|
||||||
|
|
||||||
|
def show_detail_random(self):
|
||||||
|
return self.show_element_new_user(constants.DETAIL_RANDOM)
|
||||||
|
|
||||||
|
def show_mature_content(self):
|
||||||
|
return self.show_element_new_user(constants.MATURE_CONTENT)
|
||||||
|
|
||||||
|
def mature_content_tags(self):
|
||||||
|
mct = self.config_mature_content_tags.split(",")
|
||||||
|
return [t.strip() for t in mct]
|
||||||
|
|
||||||
|
def get_log_level(self):
|
||||||
|
return logger.get_level_name(self.config_log_level)
|
||||||
|
|
||||||
|
def get_mail_settings(self):
|
||||||
|
return {k:v for k, v in self.__dict__.items() if k.startswith('mail_')}
|
||||||
|
|
||||||
|
def set_from_dictionary(self, dictionary, field, convertor=None, default=None):
|
||||||
|
'''Possibly updates a field of this object.
|
||||||
|
The new value, if present, is grabbed from the given dictionary, and optionally passed through a convertor.
|
||||||
|
|
||||||
|
:returns: `True` if the field has changed value
|
||||||
|
'''
|
||||||
|
new_value = dictionary.get(field, default)
|
||||||
|
if new_value is None:
|
||||||
|
# log.debug("_ConfigSQL set_from_dictionary field '%s' not found", field)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if field not in self.__dict__:
|
||||||
|
log.warning("_ConfigSQL trying to set unknown field '%s' = %r", field, new_value)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if convertor is not None:
|
||||||
|
new_value = convertor(new_value)
|
||||||
|
|
||||||
|
current_value = self.__dict__.get(field)
|
||||||
|
if current_value == new_value:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# log.debug("_ConfigSQL set_from_dictionary '%s' = %r (was %r)", field, new_value, current_value)
|
||||||
|
setattr(self, field, new_value)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
'''Load all configuration values from the underlying storage.'''
|
||||||
|
s = self._read_from_storage() # type: _Settings
|
||||||
|
for k, v in s.__dict__.items():
|
||||||
|
if k[0] != '_':
|
||||||
|
if v is None:
|
||||||
|
# if the storage column has no value, apply the (possible) default
|
||||||
|
column = s.__class__.__dict__.get(k)
|
||||||
|
if column.default is not None:
|
||||||
|
v = column.default.arg
|
||||||
|
setattr(self, k, v)
|
||||||
|
|
||||||
|
if self.config_google_drive_watch_changes_response:
|
||||||
|
self.config_google_drive_watch_changes_response = json.loads(self.config_google_drive_watch_changes_response)
|
||||||
|
self.db_configured = (self.config_calibre_dir and
|
||||||
|
(not self.config_use_google_drive or os.path.exists(self.config_calibre_dir + '/metadata.db')))
|
||||||
|
logger.setup(self.config_logfile, self.config_log_level)
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
'''Apply all configuration values to the underlying storage.'''
|
||||||
|
s = self._read_from_storage() # type: _Settings
|
||||||
|
|
||||||
|
for k, v in self.__dict__.items():
|
||||||
|
if k[0] == '_':
|
||||||
|
continue
|
||||||
|
if hasattr(s, k): # and getattr(s, k, None) != v:
|
||||||
|
# log.debug("_Settings save '%s' = %r", k, v)
|
||||||
|
setattr(s, k, v)
|
||||||
|
|
||||||
|
log.debug("_ConfigSQL updating storage")
|
||||||
|
self._session.merge(s)
|
||||||
|
self._session.commit()
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def invalidate(self):
|
||||||
|
log.warning("invalidating configuration")
|
||||||
|
self.db_configured = False
|
||||||
|
self.config_calibre_dir = None
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
|
||||||
|
def _migrate_table(session, orm_class):
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
for column_name, column in orm_class.__dict__.items():
|
||||||
|
if column_name[0] != '_':
|
||||||
|
try:
|
||||||
|
session.query(column).first()
|
||||||
|
except exc.OperationalError as err:
|
||||||
|
log.debug("%s: %s", column_name, err)
|
||||||
|
column_default = "" if column.default is None else ("DEFAULT %r" % column.default.arg)
|
||||||
|
alter_table = "ALTER TABLE %s ADD COLUMN `%s` %s %s" % (orm_class.__tablename__, column_name, column.type, column_default)
|
||||||
|
session.execute(alter_table)
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
if changed:
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def _migrate_database(session):
|
||||||
|
# make sure the table is created, if it does not exist
|
||||||
|
_Base.metadata.create_all(session.bind)
|
||||||
|
_migrate_table(session, _Settings)
|
||||||
|
|
||||||
|
|
||||||
|
def load_configuration(session):
|
||||||
|
_migrate_database(session)
|
||||||
|
|
||||||
|
if not session.query(_Settings).count():
|
||||||
|
session.add(_Settings())
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
return _ConfigSQL(session)
|
@ -74,7 +74,7 @@ SIDEBAR_PUBLISHER = 1 << 12
|
|||||||
SIDEBAR_RATING = 1 << 13
|
SIDEBAR_RATING = 1 << 13
|
||||||
SIDEBAR_FORMAT = 1 << 14
|
SIDEBAR_FORMAT = 1 << 14
|
||||||
|
|
||||||
ADMIN_USER_ROLES = (ROLE_VIEWER << 1) - 1 - (ROLE_ANONYMOUS | ROLE_EDIT_SHELFS)
|
ADMIN_USER_ROLES = sum(r for r in ALL_ROLES.values()) & ~ROLE_EDIT_SHELFS & ~ROLE_ANONYMOUS
|
||||||
ADMIN_USER_SIDEBAR = (SIDEBAR_FORMAT << 1) - 1
|
ADMIN_USER_SIDEBAR = (SIDEBAR_FORMAT << 1) - 1
|
||||||
|
|
||||||
UPDATE_STABLE = 0 << 0
|
UPDATE_STABLE = 0 << 0
|
||||||
@ -109,6 +109,9 @@ EXTENSIONS_UPLOAD = {'txt', 'pdf', 'epub', 'mobi', 'azw', 'azw3', 'cbr', 'cbz'
|
|||||||
def has_flag(value, bit_flag):
|
def has_flag(value, bit_flag):
|
||||||
return bit_flag == (bit_flag & (value or 0))
|
return bit_flag == (bit_flag & (value or 0))
|
||||||
|
|
||||||
|
def selected_roles(dictionary):
|
||||||
|
return sum(v for k, v in ALL_ROLES.items() if k in dictionary)
|
||||||
|
|
||||||
|
|
||||||
# :rtype: BookMeta
|
# :rtype: BookMeta
|
||||||
BookMeta = namedtuple('BookMeta', 'file_path, extension, title, author, cover, description, tags, series, '
|
BookMeta = namedtuple('BookMeta', 'file_path, extension, title, author, cover, description, tags, series, '
|
||||||
|
@ -24,19 +24,14 @@ import re
|
|||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
|
|
||||||
from . import config
|
from . import config
|
||||||
from .subproc_wrapper import process_open
|
from .subproc_wrapper import process_wait
|
||||||
|
|
||||||
|
|
||||||
def versionKindle():
|
def versionKindle():
|
||||||
versions = _(u'not installed')
|
versions = _(u'not installed')
|
||||||
if os.path.exists(config.config_converterpath):
|
if os.path.exists(config.config_converterpath):
|
||||||
try:
|
try:
|
||||||
p = process_open(config.config_converterpath)
|
for lines in process_wait(config.config_converterpath):
|
||||||
# p = subprocess.Popen(ub.config.config_converterpath, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
||||||
p.wait()
|
|
||||||
for lines in p.stdout.readlines():
|
|
||||||
if isinstance(lines, bytes):
|
|
||||||
lines = lines.decode('utf-8')
|
|
||||||
if re.search('Amazon kindlegen\(', lines):
|
if re.search('Amazon kindlegen\(', lines):
|
||||||
versions = lines
|
versions = lines
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -48,12 +43,7 @@ def versionCalibre():
|
|||||||
versions = _(u'not installed')
|
versions = _(u'not installed')
|
||||||
if os.path.exists(config.config_converterpath):
|
if os.path.exists(config.config_converterpath):
|
||||||
try:
|
try:
|
||||||
p = process_open([config.config_converterpath, '--version'])
|
for lines in process_wait([config.config_converterpath, '--version']):
|
||||||
# p = subprocess.Popen([ub.config.config_converterpath, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
||||||
p.wait()
|
|
||||||
for lines in p.stdout.readlines():
|
|
||||||
if isinstance(lines, bytes):
|
|
||||||
lines = lines.decode('utf-8')
|
|
||||||
if re.search('ebook-convert.*\(calibre', lines):
|
if re.search('ebook-convert.*\(calibre', lines):
|
||||||
versions = lines
|
versions = lines
|
||||||
except Exception:
|
except Exception:
|
||||||
|
98
cps/db.py
98
cps/db.py
@ -30,24 +30,10 @@ from sqlalchemy import String, Integer, Boolean
|
|||||||
from sqlalchemy.orm import relationship, sessionmaker, scoped_session
|
from sqlalchemy.orm import relationship, sessionmaker, scoped_session
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
|
||||||
from . import config, ub
|
|
||||||
|
|
||||||
|
|
||||||
session = None
|
session = None
|
||||||
cc_exceptions = ['datetime', 'comments', 'float', 'composite', 'series']
|
cc_exceptions = ['datetime', 'comments', 'float', 'composite', 'series']
|
||||||
cc_classes = None
|
cc_classes = {}
|
||||||
engine = None
|
|
||||||
|
|
||||||
|
|
||||||
# user defined sort function for calibre databases (Series, etc.)
|
|
||||||
def title_sort(title):
|
|
||||||
# calibre sort stuff
|
|
||||||
title_pat = re.compile(config.config_title_regex, re.IGNORECASE)
|
|
||||||
match = title_pat.search(title)
|
|
||||||
if match:
|
|
||||||
prep = match.group(1)
|
|
||||||
title = title.replace(prep, '') + ', ' + prep
|
|
||||||
return title.strip()
|
|
||||||
|
|
||||||
|
|
||||||
Base = declarative_base()
|
Base = declarative_base()
|
||||||
@ -325,40 +311,45 @@ class Custom_Columns(Base):
|
|||||||
return display_dict
|
return display_dict
|
||||||
|
|
||||||
|
|
||||||
def setup_db():
|
def update_title_sort(config, conn=None):
|
||||||
global engine
|
# user defined sort function for calibre databases (Series, etc.)
|
||||||
global session
|
def _title_sort(title):
|
||||||
global cc_classes
|
# calibre sort stuff
|
||||||
|
title_pat = re.compile(config.config_title_regex, re.IGNORECASE)
|
||||||
|
match = title_pat.search(title)
|
||||||
|
if match:
|
||||||
|
prep = match.group(1)
|
||||||
|
title = title.replace(prep, '') + ', ' + prep
|
||||||
|
return title.strip()
|
||||||
|
|
||||||
if config.config_calibre_dir is None or config.config_calibre_dir == u'':
|
conn = conn or session.connection().connection.connection
|
||||||
content = ub.session.query(ub.Settings).first()
|
conn.create_function("title_sort", 1, _title_sort)
|
||||||
content.config_calibre_dir = None
|
|
||||||
content.db_configured = False
|
|
||||||
ub.session.commit()
|
def setup_db(config):
|
||||||
config.loadSettings()
|
dispose()
|
||||||
|
|
||||||
|
if not config.config_calibre_dir:
|
||||||
|
config.invalidate()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
dbpath = os.path.join(config.config_calibre_dir, "metadata.db")
|
dbpath = os.path.join(config.config_calibre_dir, "metadata.db")
|
||||||
try:
|
|
||||||
if not os.path.exists(dbpath):
|
if not os.path.exists(dbpath):
|
||||||
raise
|
config.invalidate()
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
engine = create_engine('sqlite:///{0}'.format(dbpath),
|
engine = create_engine('sqlite:///{0}'.format(dbpath),
|
||||||
echo=False,
|
echo=False,
|
||||||
isolation_level="SERIALIZABLE",
|
isolation_level="SERIALIZABLE",
|
||||||
connect_args={'check_same_thread': False})
|
connect_args={'check_same_thread': False})
|
||||||
conn = engine.connect()
|
conn = engine.connect()
|
||||||
except Exception:
|
except:
|
||||||
content = ub.session.query(ub.Settings).first()
|
config.invalidate()
|
||||||
content.config_calibre_dir = None
|
|
||||||
content.db_configured = False
|
|
||||||
ub.session.commit()
|
|
||||||
config.loadSettings()
|
|
||||||
return False
|
return False
|
||||||
content = ub.session.query(ub.Settings).first()
|
|
||||||
content.db_configured = True
|
config.db_configured = True
|
||||||
ub.session.commit()
|
update_title_sort(config, conn.connection)
|
||||||
config.loadSettings()
|
|
||||||
conn.connection.create_function('title_sort', 1, title_sort)
|
|
||||||
# conn.connection.create_function('lower', 1, lcase)
|
# conn.connection.create_function('lower', 1, lcase)
|
||||||
# conn.connection.create_function('upper', 1, ucase)
|
# conn.connection.create_function('upper', 1, ucase)
|
||||||
|
|
||||||
@ -367,7 +358,6 @@ def setup_db():
|
|||||||
|
|
||||||
cc_ids = []
|
cc_ids = []
|
||||||
books_custom_column_links = {}
|
books_custom_column_links = {}
|
||||||
cc_classes = {}
|
|
||||||
for row in cc:
|
for row in cc:
|
||||||
if row.datatype not in cc_exceptions:
|
if row.datatype not in cc_exceptions:
|
||||||
books_custom_column_links[row.id] = Table('books_custom_column_' + str(row.id) + '_link', Base.metadata,
|
books_custom_column_links[row.id] = Table('books_custom_column_' + str(row.id) + '_link', Base.metadata,
|
||||||
@ -406,8 +396,38 @@ def setup_db():
|
|||||||
backref='books'))
|
backref='books'))
|
||||||
|
|
||||||
|
|
||||||
|
global session
|
||||||
Session = scoped_session(sessionmaker(autocommit=False,
|
Session = scoped_session(sessionmaker(autocommit=False,
|
||||||
autoflush=False,
|
autoflush=False,
|
||||||
bind=engine))
|
bind=engine))
|
||||||
session = Session()
|
session = Session()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def dispose():
|
||||||
|
global session
|
||||||
|
|
||||||
|
engine = None
|
||||||
|
if session:
|
||||||
|
engine = session.bind
|
||||||
|
try: session.close()
|
||||||
|
except: pass
|
||||||
|
session = None
|
||||||
|
|
||||||
|
if engine:
|
||||||
|
try: engine.dispose()
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
for attr in list(Books.__dict__.keys()):
|
||||||
|
if attr.startswith("custom_column_"):
|
||||||
|
delattr(Books, attr)
|
||||||
|
|
||||||
|
for db_class in cc_classes.values():
|
||||||
|
Base.metadata.remove(db_class.__table__)
|
||||||
|
cc_classes.clear()
|
||||||
|
|
||||||
|
for table in reversed(Base.metadata.sorted_tables):
|
||||||
|
name = table.key
|
||||||
|
if name.startswith("custom_column_") or name.startswith("books_custom_column_"):
|
||||||
|
if table is not None:
|
||||||
|
Base.metadata.remove(table)
|
||||||
|
@ -33,7 +33,7 @@ from flask_babel import gettext as _
|
|||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
from . import constants, logger, isoLanguages, gdriveutils, uploader, helper
|
from . import constants, logger, isoLanguages, gdriveutils, uploader, helper
|
||||||
from . import config, get_locale, db, ub, global_WorkerThread, language_table
|
from . import config, get_locale, db, ub, global_WorkerThread
|
||||||
from .helper import order_authors, common_filters
|
from .helper import order_authors, common_filters
|
||||||
from .web import login_required_if_no_ano, render_title_template, edit_required, upload_required, login_required
|
from .web import login_required_if_no_ano, render_title_template, edit_required, upload_required, login_required
|
||||||
|
|
||||||
@ -206,7 +206,7 @@ def delete_book(book_id, book_format):
|
|||||||
|
|
||||||
|
|
||||||
def render_edit_book(book_id):
|
def render_edit_book(book_id):
|
||||||
db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort)
|
db.update_title_sort(config)
|
||||||
cc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
|
cc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
|
||||||
book = db.session.query(db.Books)\
|
book = db.session.query(db.Books)\
|
||||||
.filter(db.Books.id == book_id).filter(common_filters()).first()
|
.filter(db.Books.id == book_id).filter(common_filters()).first()
|
||||||
@ -215,8 +215,8 @@ def render_edit_book(book_id):
|
|||||||
flash(_(u"Error opening eBook. File does not exist or file is not accessible"), category="error")
|
flash(_(u"Error opening eBook. File does not exist or file is not accessible"), category="error")
|
||||||
return redirect(url_for("web.index"))
|
return redirect(url_for("web.index"))
|
||||||
|
|
||||||
for indx in range(0, len(book.languages)):
|
for lang in book.languages:
|
||||||
book.languages[indx].language_name = language_table[get_locale()][book.languages[indx].lang_code]
|
lang.language_name = isoLanguages.get_language_name(get_locale(), lang.lang_code)
|
||||||
|
|
||||||
book = order_authors(book)
|
book = order_authors(book)
|
||||||
|
|
||||||
@ -354,7 +354,7 @@ def upload_single_file(request, book, book_id):
|
|||||||
db_format = db.Data(book_id, file_ext.upper(), file_size, file_name)
|
db_format = db.Data(book_id, file_ext.upper(), file_size, file_name)
|
||||||
db.session.add(db_format)
|
db.session.add(db_format)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort)
|
db.update_title_sort(config)
|
||||||
|
|
||||||
# Queue uploader info
|
# Queue uploader info
|
||||||
uploadText=_(u"File format %(ext)s added to %(book)s", ext=file_ext.upper(), book=book.title)
|
uploadText=_(u"File format %(ext)s added to %(book)s", ext=file_ext.upper(), book=book.title)
|
||||||
@ -385,7 +385,7 @@ def edit_book(book_id):
|
|||||||
return render_edit_book(book_id)
|
return render_edit_book(book_id)
|
||||||
|
|
||||||
# create the function for sorting...
|
# create the function for sorting...
|
||||||
db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort)
|
db.update_title_sort(config)
|
||||||
book = db.session.query(db.Books)\
|
book = db.session.query(db.Books)\
|
||||||
.filter(db.Books.id == book_id).filter(common_filters()).first()
|
.filter(db.Books.id == book_id).filter(common_filters()).first()
|
||||||
|
|
||||||
@ -484,17 +484,12 @@ def edit_book(book_id):
|
|||||||
|
|
||||||
# handle book languages
|
# handle book languages
|
||||||
input_languages = to_save["languages"].split(',')
|
input_languages = to_save["languages"].split(',')
|
||||||
input_languages = [x.strip().lower() for x in input_languages if x != '']
|
unknown_languages = []
|
||||||
input_l = []
|
input_l = isoLanguages.get_language_codes(get_locale(), input_languages, unknown_languages)
|
||||||
invers_lang_table = [x.lower() for x in language_table[get_locale()].values()]
|
for l in unknown_languages:
|
||||||
for lang in input_languages:
|
log.error('%s is not a valid language', l)
|
||||||
try:
|
flash(_(u"%(langname)s is not a valid language", langname=l), category="error")
|
||||||
res = list(language_table[get_locale()].keys())[invers_lang_table.index(lang)]
|
modify_database_object(list(input_l), book.languages, db.Languages, db.session, 'languages')
|
||||||
input_l.append(res)
|
|
||||||
except ValueError:
|
|
||||||
log.error('%s is not a valid language', lang)
|
|
||||||
flash(_(u"%(langname)s is not a valid language", langname=lang), category="error")
|
|
||||||
modify_database_object(input_l, book.languages, db.Languages, db.session, 'languages')
|
|
||||||
|
|
||||||
# handle book ratings
|
# handle book ratings
|
||||||
if to_save["rating"].strip():
|
if to_save["rating"].strip():
|
||||||
@ -546,7 +541,7 @@ def upload():
|
|||||||
if request.method == 'POST' and 'btn-upload' in request.files:
|
if request.method == 'POST' and 'btn-upload' in request.files:
|
||||||
for requested_file in request.files.getlist("btn-upload"):
|
for requested_file in request.files.getlist("btn-upload"):
|
||||||
# create the function for sorting...
|
# create the function for sorting...
|
||||||
db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort)
|
db.update_title_sort(config)
|
||||||
db.session.connection().connection.connection.create_function('uuid4', 0, lambda: str(uuid4()))
|
db.session.connection().connection.connection.create_function('uuid4', 0, lambda: str(uuid4()))
|
||||||
|
|
||||||
# check if file extension is correct
|
# check if file extension is correct
|
||||||
@ -659,7 +654,7 @@ def upload():
|
|||||||
|
|
||||||
# save data to database, reread data
|
# save data to database, reread data
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort)
|
db.update_title_sort(config)
|
||||||
book = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
|
book = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
|
||||||
|
|
||||||
# upload book to gdrive if nesseccary and add "(bookid)" to folder name
|
# upload book to gdrive if nesseccary and add "(bookid)" to folder name
|
||||||
|
@ -39,7 +39,7 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
from . import logger, gdriveutils, config, ub, db
|
from . import logger, gdriveutils, config, db
|
||||||
from .web import admin_required
|
from .web import admin_required
|
||||||
|
|
||||||
|
|
||||||
@ -94,12 +94,9 @@ def watch_gdrive():
|
|||||||
try:
|
try:
|
||||||
result = gdriveutils.watchChange(gdriveutils.Gdrive.Instance().drive, notification_id,
|
result = gdriveutils.watchChange(gdriveutils.Gdrive.Instance().drive, notification_id,
|
||||||
'web_hook', address, gdrive_watch_callback_token, current_milli_time() + 604800*1000)
|
'web_hook', address, gdrive_watch_callback_token, current_milli_time() + 604800*1000)
|
||||||
settings = ub.session.query(ub.Settings).first()
|
config.config_google_drive_watch_changes_response = json.dumps(result)
|
||||||
settings.config_google_drive_watch_changes_response = json.dumps(result)
|
# after save(), config_google_drive_watch_changes_response will be a json object, not string
|
||||||
ub.session.merge(settings)
|
config.save()
|
||||||
ub.session.commit()
|
|
||||||
settings = ub.session.query(ub.Settings).first()
|
|
||||||
config.loadSettings()
|
|
||||||
except HttpError as e:
|
except HttpError as e:
|
||||||
reason=json.loads(e.content)['error']['errors'][0]
|
reason=json.loads(e.content)['error']['errors'][0]
|
||||||
if reason['reason'] == u'push.webhookUrlUnauthorized':
|
if reason['reason'] == u'push.webhookUrlUnauthorized':
|
||||||
@ -121,11 +118,8 @@ def revoke_watch_gdrive():
|
|||||||
last_watch_response['resourceId'])
|
last_watch_response['resourceId'])
|
||||||
except HttpError:
|
except HttpError:
|
||||||
pass
|
pass
|
||||||
settings = ub.session.query(ub.Settings).first()
|
config.config_google_drive_watch_changes_response = None
|
||||||
settings.config_google_drive_watch_changes_response = None
|
config.save()
|
||||||
ub.session.merge(settings)
|
|
||||||
ub.session.commit()
|
|
||||||
config.loadSettings()
|
|
||||||
return redirect(url_for('admin.configuration'))
|
return redirect(url_for('admin.configuration'))
|
||||||
|
|
||||||
|
|
||||||
@ -157,7 +151,7 @@ def on_received_watch_confirmation():
|
|||||||
log.info('Setting up new DB')
|
log.info('Setting up new DB')
|
||||||
# prevent error on windows, as os.rename does on exisiting files
|
# prevent error on windows, as os.rename does on exisiting files
|
||||||
move(os.path.join(tmpDir, "tmp_metadata.db"), dbpath)
|
move(os.path.join(tmpDir, "tmp_metadata.db"), dbpath)
|
||||||
db.setup_db()
|
db.setup_db(config)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception(e)
|
log.exception(e)
|
||||||
updateMetaData()
|
updateMetaData()
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
from __future__ import division, print_function, unicode_literals
|
from __future__ import division, print_function, unicode_literals
|
||||||
import os
|
import os
|
||||||
|
import json
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from flask import Response, stream_with_context
|
from flask import Response, stream_with_context
|
||||||
@ -79,6 +80,9 @@ class Singleton:
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
self._instance = self._decorated()
|
self._instance = self._decorated()
|
||||||
return self._instance
|
return self._instance
|
||||||
|
except ImportError as e:
|
||||||
|
log.debug(e)
|
||||||
|
return None
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
raise TypeError('Singletons must be accessed through `Instance()`.')
|
raise TypeError('Singletons must be accessed through `Instance()`.')
|
||||||
@ -534,3 +538,51 @@ def do_gdrive_download(df, headers):
|
|||||||
log.warning('An error occurred: %s', resp)
|
log.warning('An error occurred: %s', resp)
|
||||||
return
|
return
|
||||||
return Response(stream_with_context(stream()), headers=headers)
|
return Response(stream_with_context(stream()), headers=headers)
|
||||||
|
|
||||||
|
|
||||||
|
_SETTINGS_YAML_TEMPLATE = """
|
||||||
|
client_config_backend: settings
|
||||||
|
client_config_file: %(client_file)s
|
||||||
|
client_config:
|
||||||
|
client_id: %(client_id)s
|
||||||
|
client_secret: %(client_secret)s
|
||||||
|
redirect_uri: %(redirect_uri)s
|
||||||
|
|
||||||
|
save_credentials: True
|
||||||
|
save_credentials_backend: file
|
||||||
|
save_credentials_file: %(credential)s
|
||||||
|
|
||||||
|
get_refresh_token: True
|
||||||
|
|
||||||
|
oauth_scope:
|
||||||
|
- https://www.googleapis.com/auth/drive
|
||||||
|
"""
|
||||||
|
|
||||||
|
def update_settings(client_id, client_secret, redirect_uri):
|
||||||
|
if redirect_uri.endswith('/'):
|
||||||
|
redirect_uri = redirect_uri[:-1]
|
||||||
|
config_params = {
|
||||||
|
'client_file': CLIENT_SECRETS,
|
||||||
|
'client_id': client_id,
|
||||||
|
'client_secret': client_secret,
|
||||||
|
'redirect_uri': redirect_uri,
|
||||||
|
'credential': CREDENTIALS
|
||||||
|
}
|
||||||
|
|
||||||
|
with open(SETTINGS_YAML, 'w') as f:
|
||||||
|
f.write(_SETTINGS_YAML_TEMPLATE % config_params)
|
||||||
|
|
||||||
|
|
||||||
|
def get_error_text(client_secrets=None):
|
||||||
|
if not gdrive_support:
|
||||||
|
return 'Import of optional Google Drive requirements missing'
|
||||||
|
|
||||||
|
if not os.path.isfile(CLIENT_SECRETS):
|
||||||
|
return 'client_secrets.json is missing or not readable'
|
||||||
|
|
||||||
|
with open(CLIENT_SECRETS, 'r') as settings:
|
||||||
|
filedata = json.load(settings)
|
||||||
|
if 'web' not in filedata:
|
||||||
|
return 'client_secrets.json is not configured for web application'
|
||||||
|
if client_secrets:
|
||||||
|
client_secrets.update(filedata['web'])
|
||||||
|
@ -26,17 +26,16 @@ import json
|
|||||||
import mimetypes
|
import mimetypes
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
import requests
|
|
||||||
import shutil
|
import shutil
|
||||||
import time
|
import time
|
||||||
import unicodedata
|
import unicodedata
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from functools import reduce
|
|
||||||
from tempfile import gettempdir
|
from tempfile import gettempdir
|
||||||
|
|
||||||
|
import requests
|
||||||
from babel import Locale as LC
|
from babel import Locale as LC
|
||||||
from babel.core import UnknownLocaleError
|
from babel.core import UnknownLocaleError
|
||||||
from babel.dates import format_datetime, format_timedelta
|
from babel.dates import format_datetime
|
||||||
from babel.units import format_unit
|
from babel.units import format_unit
|
||||||
from flask import send_from_directory, make_response, redirect, abort
|
from flask import send_from_directory, make_response, redirect, abort
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
@ -55,12 +54,6 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
use_unidecode = False
|
use_unidecode = False
|
||||||
|
|
||||||
try:
|
|
||||||
import Levenshtein
|
|
||||||
use_levenshtein = True
|
|
||||||
except ImportError:
|
|
||||||
use_levenshtein = False
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
use_PIL = True
|
use_PIL = True
|
||||||
@ -71,7 +64,7 @@ from . import logger, config, global_WorkerThread, get_locale, db, ub, isoLangua
|
|||||||
from . import gdriveutils as gd
|
from . import gdriveutils as gd
|
||||||
from .constants import STATIC_DIR as _STATIC_DIR
|
from .constants import STATIC_DIR as _STATIC_DIR
|
||||||
from .pagination import Pagination
|
from .pagination import Pagination
|
||||||
from .subproc_wrapper import process_open
|
from .subproc_wrapper import process_wait
|
||||||
from .worker import STAT_WAITING, STAT_FAIL, STAT_STARTED, STAT_FINISH_SUCCESS
|
from .worker import STAT_WAITING, STAT_FAIL, STAT_STARTED, STAT_FINISH_SUCCESS
|
||||||
from .worker import TASK_EMAIL, TASK_CONVERT, TASK_UPLOAD, TASK_CONVERT_ANY
|
from .worker import TASK_EMAIL, TASK_CONVERT, TASK_UPLOAD, TASK_CONVERT_ANY
|
||||||
|
|
||||||
@ -110,7 +103,7 @@ def convert_book_format(book_id, calibrepath, old_book_format, new_book_format,
|
|||||||
if os.path.exists(file_path + "." + old_book_format.lower()):
|
if os.path.exists(file_path + "." + old_book_format.lower()):
|
||||||
# read settings and append converter task to queue
|
# read settings and append converter task to queue
|
||||||
if kindle_mail:
|
if kindle_mail:
|
||||||
settings = ub.get_mail_settings()
|
settings = config.get_mail_settings()
|
||||||
settings['subject'] = _('Send to Kindle') # pretranslate Subject for e-mail
|
settings['subject'] = _('Send to Kindle') # pretranslate Subject for e-mail
|
||||||
settings['body'] = _(u'This e-mail has been sent via Calibre-Web.')
|
settings['body'] = _(u'This e-mail has been sent via Calibre-Web.')
|
||||||
# text = _(u"%(format)s: %(book)s", format=new_book_format, book=book.title)
|
# text = _(u"%(format)s: %(book)s", format=new_book_format, book=book.title)
|
||||||
@ -128,7 +121,7 @@ def convert_book_format(book_id, calibrepath, old_book_format, new_book_format,
|
|||||||
|
|
||||||
|
|
||||||
def send_test_mail(kindle_mail, user_name):
|
def send_test_mail(kindle_mail, user_name):
|
||||||
global_WorkerThread.add_email(_(u'Calibre-Web test e-mail'),None, None, ub.get_mail_settings(),
|
global_WorkerThread.add_email(_(u'Calibre-Web test e-mail'),None, None, config.get_mail_settings(),
|
||||||
kindle_mail, user_name, _(u"Test e-mail"),
|
kindle_mail, user_name, _(u"Test e-mail"),
|
||||||
_(u'This e-mail has been sent via Calibre-Web.'))
|
_(u'This e-mail has been sent via Calibre-Web.'))
|
||||||
return
|
return
|
||||||
@ -145,7 +138,7 @@ def send_registration_mail(e_mail, user_name, default_password, resend=False):
|
|||||||
text += "Don't forget to change your password after first login.\r\n"
|
text += "Don't forget to change your password after first login.\r\n"
|
||||||
text += "Sincerely\r\n\r\n"
|
text += "Sincerely\r\n\r\n"
|
||||||
text += "Your Calibre-Web team"
|
text += "Your Calibre-Web team"
|
||||||
global_WorkerThread.add_email(_(u'Get Started with Calibre-Web'),None, None, ub.get_mail_settings(),
|
global_WorkerThread.add_email(_(u'Get Started with Calibre-Web'),None, None, config.get_mail_settings(),
|
||||||
e_mail, None, _(u"Registration e-mail for user: %(name)s", name=user_name), text)
|
e_mail, None, _(u"Registration e-mail for user: %(name)s", name=user_name), text)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -218,7 +211,7 @@ def send_mail(book_id, book_format, convert, kindle_mail, calibrepath, user_id):
|
|||||||
for entry in iter(book.data):
|
for entry in iter(book.data):
|
||||||
if entry.format.upper() == book_format.upper():
|
if entry.format.upper() == book_format.upper():
|
||||||
result = entry.name + '.' + book_format.lower()
|
result = entry.name + '.' + book_format.lower()
|
||||||
global_WorkerThread.add_email(_(u"Send to Kindle"), book.path, result, ub.get_mail_settings(),
|
global_WorkerThread.add_email(_(u"Send to Kindle"), book.path, result, config.get_mail_settings(),
|
||||||
kindle_mail, user_id, _(u"E-mail: %(book)s", book=book.title),
|
kindle_mail, user_id, _(u"E-mail: %(book)s", book=book.title),
|
||||||
_(u'This e-mail has been sent via Calibre-Web.'))
|
_(u'This e-mail has been sent via Calibre-Web.'))
|
||||||
return
|
return
|
||||||
@ -561,27 +554,23 @@ def do_download_file(book, book_format, data, headers):
|
|||||||
|
|
||||||
|
|
||||||
def check_unrar(unrarLocation):
|
def check_unrar(unrarLocation):
|
||||||
error = False
|
if not unrarLocation:
|
||||||
if os.path.exists(unrarLocation):
|
return
|
||||||
|
|
||||||
|
if not os.path.exists(unrarLocation):
|
||||||
|
return 'Unrar binary file not found'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
unrarLocation = unrarLocation.encode(sys.getfilesystemencoding())
|
unrarLocation = unrarLocation.encode(sys.getfilesystemencoding())
|
||||||
p = process_open(unrarLocation)
|
for lines in process_wait(unrarLocation):
|
||||||
p.wait()
|
value = re.search('UNRAR (.*) freeware', lines)
|
||||||
for lines in p.stdout.readlines():
|
|
||||||
if isinstance(lines, bytes):
|
|
||||||
lines = lines.decode('utf-8')
|
|
||||||
value=re.search('UNRAR (.*) freeware', lines)
|
|
||||||
if value:
|
if value:
|
||||||
version = value.group(1)
|
version = value.group(1)
|
||||||
except OSError as e:
|
log.debug("unrar version %s", version)
|
||||||
error = True
|
except OSError as err:
|
||||||
log.exception(e)
|
log.exception(err)
|
||||||
version =_(u'Error excecuting UnRar')
|
return 'Error excecuting UnRar'
|
||||||
else:
|
|
||||||
version = _(u'Unrar binary file not found')
|
|
||||||
error=True
|
|
||||||
return (error, version)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -605,7 +594,7 @@ def json_serial(obj):
|
|||||||
def format_runtime(runtime):
|
def format_runtime(runtime):
|
||||||
retVal = ""
|
retVal = ""
|
||||||
if runtime.days:
|
if runtime.days:
|
||||||
retVal = format_unit(runtime.days, 'duration-day', length="long", locale=web.get_locale()) + ', '
|
retVal = format_unit(runtime.days, 'duration-day', length="long", locale=get_locale()) + ', '
|
||||||
mins, seconds = divmod(runtime.seconds, 60)
|
mins, seconds = divmod(runtime.seconds, 60)
|
||||||
hours, minutes = divmod(mins, 60)
|
hours, minutes = divmod(mins, 60)
|
||||||
# ToDo: locale.number_symbols._data['timeSeparator'] -> localize time separator ?
|
# ToDo: locale.number_symbols._data['timeSeparator'] -> localize time separator ?
|
||||||
@ -630,6 +619,9 @@ def render_task_status(tasklist):
|
|||||||
if 'starttime' not in task:
|
if 'starttime' not in task:
|
||||||
task['starttime'] = ""
|
task['starttime'] = ""
|
||||||
|
|
||||||
|
if 'formRuntime' not in task:
|
||||||
|
task['runtime'] = ""
|
||||||
|
else:
|
||||||
task['runtime'] = format_runtime(task['formRuntime'])
|
task['runtime'] = format_runtime(task['formRuntime'])
|
||||||
|
|
||||||
# localize the task status
|
# localize the task status
|
||||||
@ -754,28 +746,6 @@ def get_search_results(term):
|
|||||||
func.lower(db.Books.title).ilike("%" + term + "%")
|
func.lower(db.Books.title).ilike("%" + term + "%")
|
||||||
)).all()
|
)).all()
|
||||||
|
|
||||||
def get_unique_other_books(library_books, author_books):
|
|
||||||
# Get all identifiers (ISBN, Goodreads, etc) and filter author's books by that list so we show fewer duplicates
|
|
||||||
# Note: Not all images will be shown, even though they're available on Goodreads.com.
|
|
||||||
# See https://www.goodreads.com/topic/show/18213769-goodreads-book-images
|
|
||||||
identifiers = reduce(lambda acc, book: acc + map(lambda identifier: identifier.val, book.identifiers),
|
|
||||||
library_books, [])
|
|
||||||
other_books = filter(lambda book: book.isbn not in identifiers and book.gid["#text"] not in identifiers,
|
|
||||||
author_books)
|
|
||||||
|
|
||||||
# Fuzzy match book titles
|
|
||||||
if use_levenshtein:
|
|
||||||
library_titles = reduce(lambda acc, book: acc + [book.title], library_books, [])
|
|
||||||
other_books = filter(lambda author_book: not filter(
|
|
||||||
lambda library_book:
|
|
||||||
# Remove items in parentheses before comparing
|
|
||||||
Levenshtein.ratio(re.sub(r"\(.*\)", "", author_book.title), library_book) > 0.7,
|
|
||||||
library_titles
|
|
||||||
), other_books)
|
|
||||||
|
|
||||||
return other_books
|
|
||||||
|
|
||||||
|
|
||||||
def get_cc_columns():
|
def get_cc_columns():
|
||||||
tmpcc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
|
tmpcc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
|
||||||
if config.config_columns_to_ignore:
|
if config.config_columns_to_ignore:
|
||||||
@ -802,10 +772,7 @@ def get_download_link(book_id, book_format):
|
|||||||
file_name = book.authors[0].name + '_' + file_name
|
file_name = book.authors[0].name + '_' + file_name
|
||||||
file_name = get_valid_filename(file_name)
|
file_name = get_valid_filename(file_name)
|
||||||
headers = Headers()
|
headers = Headers()
|
||||||
try:
|
headers["Content-Type"] = mimetypes.types_map.get('.' + book_format, "application/octet-stream")
|
||||||
headers["Content-Type"] = mimetypes.types_map['.' + book_format]
|
|
||||||
except KeyError:
|
|
||||||
headers["Content-Type"] = "application/octet-stream"
|
|
||||||
headers["Content-Disposition"] = "attachment; filename*=UTF-8''%s.%s" % (quote(file_name.encode('utf-8')),
|
headers["Content-Disposition"] = "attachment; filename*=UTF-8''%s.%s" % (quote(file_name.encode('utf-8')),
|
||||||
book_format)
|
book_format)
|
||||||
return do_download_file(book, book_format, data, headers)
|
return do_download_file(book, book_format, data, headers)
|
||||||
|
@ -18,6 +18,14 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from __future__ import division, print_function, unicode_literals
|
from __future__ import division, print_function, unicode_literals
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
try:
|
||||||
|
import cPickle
|
||||||
|
except ImportError:
|
||||||
|
import pickle as cPickle
|
||||||
|
|
||||||
|
from .constants import TRANSLATIONS_DIR as _TRANSLATIONS_DIR
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -33,14 +41,43 @@ except ImportError:
|
|||||||
__version__ = "? (PyCountry)"
|
__version__ = "? (PyCountry)"
|
||||||
|
|
||||||
def _copy_fields(l):
|
def _copy_fields(l):
|
||||||
l.part1 = l.alpha_2
|
l.part1 = getattr(l, 'alpha_2', None)
|
||||||
l.part3 = l.alpha_3
|
l.part3 = getattr(l, 'alpha_3', None)
|
||||||
return l
|
return l
|
||||||
|
|
||||||
def get(name=None, part1=None, part3=None):
|
def get(name=None, part1=None, part3=None):
|
||||||
if (part3 is not None):
|
if part3 is not None:
|
||||||
return _copy_fields(pyc_languages.get(alpha_3=part3))
|
return _copy_fields(pyc_languages.get(alpha_3=part3))
|
||||||
if (part1 is not None):
|
if part1 is not None:
|
||||||
return _copy_fields(pyc_languages.get(alpha_2=part1))
|
return _copy_fields(pyc_languages.get(alpha_2=part1))
|
||||||
if (name is not None):
|
if name is not None:
|
||||||
return _copy_fields(pyc_languages.get(name=name))
|
return _copy_fields(pyc_languages.get(name=name))
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(os.path.join(_TRANSLATIONS_DIR, 'iso639.pickle'), 'rb') as f:
|
||||||
|
_LANGUAGES = cPickle.load(f)
|
||||||
|
except cPickle.UnpicklingError as error:
|
||||||
|
print("Can't read file cps/translations/iso639.pickle: %s" % error)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def get_language_names(locale):
|
||||||
|
return _LANGUAGES.get(locale)
|
||||||
|
|
||||||
|
|
||||||
|
def get_language_name(locale, lang_code):
|
||||||
|
return get_language_names(locale)[lang_code]
|
||||||
|
|
||||||
|
|
||||||
|
def get_language_codes(locale, language_names, remainder=None):
|
||||||
|
language_names = set(x.strip().lower() for x in language_names if x)
|
||||||
|
|
||||||
|
for k, v in get_language_names(locale).items():
|
||||||
|
v = v.lower()
|
||||||
|
if v in language_names:
|
||||||
|
language_names.remove(v)
|
||||||
|
yield k
|
||||||
|
|
||||||
|
if remainder is not None:
|
||||||
|
remainder.extend(language_names)
|
||||||
|
@ -71,11 +71,7 @@ def shortentitle_filter(s, nchar=20):
|
|||||||
|
|
||||||
@jinjia.app_template_filter('mimetype')
|
@jinjia.app_template_filter('mimetype')
|
||||||
def mimetype_filter(val):
|
def mimetype_filter(val):
|
||||||
try:
|
return mimetypes.types_map.get('.' + val, 'application/octet-stream')
|
||||||
s = mimetypes.types_map['.' + val]
|
|
||||||
except Exception:
|
|
||||||
s = 'application/octet-stream'
|
|
||||||
return s
|
|
||||||
|
|
||||||
|
|
||||||
@jinjia.app_template_filter('formatdate')
|
@jinjia.app_template_filter('formatdate')
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
|
|
||||||
# Copyright (C) 2019 Krakinou
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
from __future__ import division, print_function, unicode_literals
|
|
||||||
import base64
|
|
||||||
|
|
||||||
try:
|
|
||||||
from flask_simpleldap import LDAP # , LDAPException
|
|
||||||
ldap_support = True
|
|
||||||
except ImportError:
|
|
||||||
ldap_support = False
|
|
||||||
|
|
||||||
from . import config, logger
|
|
||||||
|
|
||||||
log = logger.create()
|
|
||||||
|
|
||||||
class Ldap():
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.ldap = None
|
|
||||||
return
|
|
||||||
|
|
||||||
def init_app(self, app):
|
|
||||||
if ldap_support and config.config_login_type == 1:
|
|
||||||
app.config['LDAP_HOST'] = config.config_ldap_provider_url
|
|
||||||
app.config['LDAP_PORT'] = config.config_ldap_port
|
|
||||||
app.config['LDAP_SCHEMA'] = config.config_ldap_schema
|
|
||||||
app.config['LDAP_USERNAME'] = config.config_ldap_user_object.replace('%s', config.config_ldap_serv_username)\
|
|
||||||
+ ',' + config.config_ldap_dn
|
|
||||||
app.config['LDAP_PASSWORD'] = base64.b64decode(config.config_ldap_serv_password)
|
|
||||||
if config.config_ldap_use_ssl:
|
|
||||||
app.config['LDAP_USE_SSL'] = True
|
|
||||||
if config.config_ldap_use_tls:
|
|
||||||
app.config['LDAP_USE_TLS'] = True
|
|
||||||
app.config['LDAP_REQUIRE_CERT'] = config.config_ldap_require_cert
|
|
||||||
if config.config_ldap_require_cert:
|
|
||||||
app.config['LDAP_CERT_PATH'] = config.config_ldap_cert_path
|
|
||||||
app.config['LDAP_BASE_DN'] = config.config_ldap_dn
|
|
||||||
app.config['LDAP_USER_OBJECT_FILTER'] = config.config_ldap_user_object
|
|
||||||
if config.config_ldap_openldap:
|
|
||||||
app.config['LDAP_OPENLDAP'] = True
|
|
||||||
|
|
||||||
# app.config['LDAP_BASE_DN'] = 'ou=users,dc=yunohost,dc=org'
|
|
||||||
# app.config['LDAP_USER_OBJECT_FILTER'] = '(uid=%s)'
|
|
||||||
self.ldap = LDAP(app)
|
|
||||||
|
|
||||||
elif config.config_login_type == 1 and not ldap_support:
|
|
||||||
log.error('Cannot activate ldap support, did you run \'pip install --target vendor -r optional-requirements.txt\'?')
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def ldap_supported(cls):
|
|
||||||
return ldap_support
|
|
@ -157,3 +157,8 @@ class StderrLogger(object):
|
|||||||
self.buffer += message
|
self.buffer += message
|
||||||
except Exception:
|
except Exception:
|
||||||
self.log.debug("Logging Error")
|
self.log.debug("Logging Error")
|
||||||
|
|
||||||
|
|
||||||
|
# if debugging, start logging to stderr immediately
|
||||||
|
if os.environ.get('FLASK_DEBUG', None):
|
||||||
|
setup(LOG_TO_STDERR, logging.DEBUG)
|
||||||
|
@ -24,7 +24,6 @@
|
|||||||
from __future__ import division, print_function, unicode_literals
|
from __future__ import division, print_function, unicode_literals
|
||||||
import json
|
import json
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from oauth import OAuthBackend
|
|
||||||
|
|
||||||
from flask import session, request, make_response, abort
|
from flask import session, request, make_response, abort
|
||||||
from flask import Blueprint, flash, redirect, url_for
|
from flask import Blueprint, flash, redirect, url_for
|
||||||
@ -37,6 +36,7 @@ from sqlalchemy.orm.exc import NoResultFound
|
|||||||
|
|
||||||
from . import constants, logger, config, app, ub
|
from . import constants, logger, config, app, ub
|
||||||
from .web import login_required
|
from .web import login_required
|
||||||
|
from .oauth import OAuthBackend
|
||||||
# from .web import github_oauth_required
|
# from .web import github_oauth_required
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ from flask_login import current_user
|
|||||||
from sqlalchemy.sql.expression import func, text, or_, and_
|
from sqlalchemy.sql.expression import func, text, or_, and_
|
||||||
from werkzeug.security import check_password_hash
|
from werkzeug.security import check_password_hash
|
||||||
|
|
||||||
from . import logger, config, db, ub, ldap1
|
from . import constants, logger, config, db, ub, services
|
||||||
from .helper import fill_indexpage, get_download_link, get_book_cover
|
from .helper import fill_indexpage, get_download_link, get_book_cover
|
||||||
from .pagination import Pagination
|
from .pagination import Pagination
|
||||||
from .web import common_filters, get_search_results, render_read_books, download_required
|
from .web import common_filters, get_search_results, render_read_books, download_required
|
||||||
@ -40,7 +40,6 @@ from .web import common_filters, get_search_results, render_read_books, download
|
|||||||
opds = Blueprint('opds', __name__)
|
opds = Blueprint('opds', __name__)
|
||||||
|
|
||||||
log = logger.create()
|
log = logger.create()
|
||||||
ldap_support = ldap1.ldap_supported()
|
|
||||||
|
|
||||||
|
|
||||||
def requires_basic_auth_if_no_ano(f):
|
def requires_basic_auth_if_no_ano(f):
|
||||||
@ -51,8 +50,8 @@ def requires_basic_auth_if_no_ano(f):
|
|||||||
if not auth or not check_auth(auth.username, auth.password):
|
if not auth or not check_auth(auth.username, auth.password):
|
||||||
return authenticate()
|
return authenticate()
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
if config.config_login_type == 1 and ldap_support:
|
if config.config_login_type == constants.LOGIN_LDAP and services.ldap:
|
||||||
return ldap1.ldap.basic_auth_required(f)
|
return services.ldap.basic_auth_required(f)
|
||||||
return decorated
|
return decorated
|
||||||
|
|
||||||
|
|
||||||
|
@ -197,6 +197,7 @@ class WebServer:
|
|||||||
self.stop()
|
self.stop()
|
||||||
|
|
||||||
def stop(self, restart=False):
|
def stop(self, restart=False):
|
||||||
|
log.info("webserver stop (restart=%s)", restart)
|
||||||
self.restart = restart
|
self.restart = restart
|
||||||
if self.wsgiserver:
|
if self.wsgiserver:
|
||||||
if _GEVENT:
|
if _GEVENT:
|
||||||
|
36
cps/services/__init__.py
Normal file
36
cps/services/__init__.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
|
||||||
|
# Copyright (C) 2019 pwr
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from __future__ import division, print_function, unicode_literals
|
||||||
|
|
||||||
|
from .. import logger
|
||||||
|
|
||||||
|
|
||||||
|
log = logger.create()
|
||||||
|
|
||||||
|
|
||||||
|
try: from . import goodreads
|
||||||
|
except ImportError as err:
|
||||||
|
log.warning("goodreads: %s", err)
|
||||||
|
goodreads = None
|
||||||
|
|
||||||
|
|
||||||
|
try: from . import simpleldap as ldap
|
||||||
|
except ImportError as err:
|
||||||
|
log.warning("simpleldap: %s", err)
|
||||||
|
ldap = None
|
106
cps/services/goodreads.py
Normal file
106
cps/services/goodreads.py
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
|
||||||
|
# Copyright (C) 2018-2019 OzzieIsaacs, pwr
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from __future__ import division, print_function, unicode_literals
|
||||||
|
import time
|
||||||
|
from functools import reduce
|
||||||
|
|
||||||
|
from goodreads.client import GoodreadsClient
|
||||||
|
|
||||||
|
try: import Levenshtein
|
||||||
|
except ImportError: Levenshtein = False
|
||||||
|
|
||||||
|
from .. import logger
|
||||||
|
|
||||||
|
|
||||||
|
log = logger.create()
|
||||||
|
_client = None # type: GoodreadsClient
|
||||||
|
|
||||||
|
# GoodReads TOS allows for 24h caching of data
|
||||||
|
_CACHE_TIMEOUT = 23 * 60 * 60 # 23 hours (in seconds)
|
||||||
|
_AUTHORS_CACHE = {}
|
||||||
|
|
||||||
|
|
||||||
|
def connect(key=None, secret=None, enabled=True):
|
||||||
|
global _client
|
||||||
|
|
||||||
|
if not enabled or not key or not secret:
|
||||||
|
_client = None
|
||||||
|
return
|
||||||
|
|
||||||
|
if _client:
|
||||||
|
# make sure the configuration has not changed since last we used the client
|
||||||
|
if _client.client_key != key or _client.client_secret != secret:
|
||||||
|
_client = None
|
||||||
|
|
||||||
|
if not _client:
|
||||||
|
_client = GoodreadsClient(key, secret)
|
||||||
|
|
||||||
|
|
||||||
|
def get_author_info(author_name):
|
||||||
|
now = time.time()
|
||||||
|
author_info = _AUTHORS_CACHE.get(author_name, None)
|
||||||
|
if author_info:
|
||||||
|
if now < author_info._timestamp + _CACHE_TIMEOUT:
|
||||||
|
return author_info
|
||||||
|
# clear expired entries
|
||||||
|
del _AUTHORS_CACHE[author_name]
|
||||||
|
|
||||||
|
if not _client:
|
||||||
|
log.warning("failed to get a Goodreads client")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
author_info = _client.find_author(author_name=author_name)
|
||||||
|
except Exception as ex:
|
||||||
|
# Skip goodreads, if site is down/inaccessible
|
||||||
|
log.warning('Goodreads website is down/inaccessible? %s', ex)
|
||||||
|
return
|
||||||
|
|
||||||
|
if author_info:
|
||||||
|
author_info._timestamp = now
|
||||||
|
_AUTHORS_CACHE[author_name] = author_info
|
||||||
|
return author_info
|
||||||
|
|
||||||
|
|
||||||
|
def get_other_books(author_info, library_books=None):
|
||||||
|
# Get all identifiers (ISBN, Goodreads, etc) and filter author's books by that list so we show fewer duplicates
|
||||||
|
# Note: Not all images will be shown, even though they're available on Goodreads.com.
|
||||||
|
# See https://www.goodreads.com/topic/show/18213769-goodreads-book-images
|
||||||
|
|
||||||
|
if not author_info:
|
||||||
|
return
|
||||||
|
|
||||||
|
identifiers = []
|
||||||
|
library_titles = []
|
||||||
|
if library_books:
|
||||||
|
identifiers = list(reduce(lambda acc, book: acc + [i.val for i in book.identifiers if i.val], library_books, []))
|
||||||
|
library_titles = [book.title for book in library_books]
|
||||||
|
|
||||||
|
for book in author_info.books:
|
||||||
|
if book.isbn in identifiers:
|
||||||
|
continue
|
||||||
|
if book.gid["#text"] in identifiers:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if Levenshtein and library_titles:
|
||||||
|
goodreads_title = book._book_dict['title_without_series']
|
||||||
|
if any(Levenshtein.ratio(goodreads_title, title) > 0.7 for title in library_titles):
|
||||||
|
continue
|
||||||
|
|
||||||
|
yield book
|
77
cps/services/simpleldap.py
Normal file
77
cps/services/simpleldap.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
|
||||||
|
# Copyright (C) 2018-2019 OzzieIsaacs, pwr
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from __future__ import division, print_function, unicode_literals
|
||||||
|
import base64
|
||||||
|
|
||||||
|
from flask_simpleldap import LDAP
|
||||||
|
from ldap import SERVER_DOWN, INVALID_CREDENTIALS
|
||||||
|
|
||||||
|
from .. import constants, logger
|
||||||
|
|
||||||
|
|
||||||
|
log = logger.create()
|
||||||
|
_ldap = None
|
||||||
|
|
||||||
|
|
||||||
|
def init_app(app, config):
|
||||||
|
global _ldap
|
||||||
|
|
||||||
|
if config.config_login_type != constants.LOGIN_LDAP:
|
||||||
|
_ldap = None
|
||||||
|
return
|
||||||
|
|
||||||
|
app.config['LDAP_HOST'] = config.config_ldap_provider_url
|
||||||
|
app.config['LDAP_PORT'] = config.config_ldap_port
|
||||||
|
app.config['LDAP_SCHEMA'] = config.config_ldap_schema
|
||||||
|
app.config['LDAP_USERNAME'] = config.config_ldap_user_object.replace('%s', config.config_ldap_serv_username)\
|
||||||
|
+ ',' + config.config_ldap_dn
|
||||||
|
app.config['LDAP_PASSWORD'] = base64.b64decode(config.config_ldap_serv_password)
|
||||||
|
app.config['LDAP_REQUIRE_CERT'] = bool(config.config_ldap_require_cert)
|
||||||
|
if config.config_ldap_require_cert:
|
||||||
|
app.config['LDAP_CERT_PATH'] = config.config_ldap_cert_path
|
||||||
|
app.config['LDAP_BASE_DN'] = config.config_ldap_dn
|
||||||
|
app.config['LDAP_USER_OBJECT_FILTER'] = config.config_ldap_user_object
|
||||||
|
app.config['LDAP_USE_SSL'] = bool(config.config_ldap_use_ssl)
|
||||||
|
app.config['LDAP_USE_TLS'] = bool(config.config_ldap_use_tls)
|
||||||
|
app.config['LDAP_OPENLDAP'] = bool(config.config_ldap_openldap)
|
||||||
|
|
||||||
|
# app.config['LDAP_BASE_DN'] = 'ou=users,dc=yunohost,dc=org'
|
||||||
|
# app.config['LDAP_USER_OBJECT_FILTER'] = '(uid=%s)'
|
||||||
|
_ldap = LDAP(app)
|
||||||
|
|
||||||
|
|
||||||
|
def basic_auth_required(func):
|
||||||
|
return _ldap.basic_auth_required(func)
|
||||||
|
|
||||||
|
|
||||||
|
def bind_user(username, password):
|
||||||
|
'''Attempts a LDAP login.
|
||||||
|
|
||||||
|
:returns: True if login succeeded, False if login failed, None if server unavailable.
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
result = _ldap.bind_user(username, password)
|
||||||
|
log.debug("LDAP login '%s': %r", username, result)
|
||||||
|
return result is not None
|
||||||
|
except SERVER_DOWN as ex:
|
||||||
|
log.warning('LDAP Server down: %s', ex)
|
||||||
|
return None
|
||||||
|
except INVALID_CREDENTIALS as ex:
|
||||||
|
log.info("LDAP login '%s' failed: %s", username, ex)
|
||||||
|
return False
|
@ -43,3 +43,13 @@ def process_open(command, quotes=(), env=None, sout=subprocess.PIPE, serr=subpro
|
|||||||
exc_command = [x for x in command]
|
exc_command = [x for x in command]
|
||||||
|
|
||||||
return subprocess.Popen(exc_command, shell=False, stdout=sout, stderr=serr, universal_newlines=True, env=env)
|
return subprocess.Popen(exc_command, shell=False, stdout=sout, stderr=serr, universal_newlines=True, env=env)
|
||||||
|
|
||||||
|
|
||||||
|
def process_wait(command, serr=subprocess.PIPE):
|
||||||
|
'''Run command, wait for process to terminate, and return an iterator over lines of its output.'''
|
||||||
|
p = process_open(command, serr=serr)
|
||||||
|
p.wait()
|
||||||
|
for l in p.stdout.readlines():
|
||||||
|
if isinstance(l, bytes):
|
||||||
|
l = l.decode('utf-8')
|
||||||
|
yield l
|
||||||
|
@ -69,7 +69,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-6 col-sm-6">{{_('Log level')}}</div>
|
<div class="col-xs-6 col-sm-6">{{_('Log level')}}</div>
|
||||||
<div class="col-xs-6 col-sm-6">{{config.get_Log_Level()}}</div>
|
<div class="col-xs-6 col-sm-6">{{config.get_log_level()}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-6 col-sm-6">{{_('Port')}}</div>
|
<div class="col-xs-6 col-sm-6">{{_('Port')}}</div>
|
||||||
|
@ -94,8 +94,8 @@
|
|||||||
<input type="text" class="form-control" name="config_keyfile" id="config_keyfile" value="{% if config.config_keyfile != None %}{{ config.config_keyfile }}{% endif %}" autocomplete="off">
|
<input type="text" class="form-control" name="config_keyfile" id="config_keyfile" value="{% if config.config_keyfile != None %}{{ config.config_keyfile }}{% endif %}" autocomplete="off">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="config_updater">{{_('Update channel')}}</label>
|
<label for="config_updatechannel">{{_('Update channel')}}</label>
|
||||||
<select name="config_updater" id="config_updater" class="form-control">
|
<select name="config_updatechannel" id="config_updatechannel" class="form-control">
|
||||||
<option value="0" {% if config.config_updatechannel == 0 %}selected{% endif %}>{{_('Stable')}}</option>
|
<option value="0" {% if config.config_updatechannel == 0 %}selected{% endif %}>{{_('Stable')}}</option>
|
||||||
<!--option value="1" {% if config.config_updatechannel == 1 %}selected{% endif %}>{{_('Stable (Automatic)')}}</option-->
|
<!--option value="1" {% if config.config_updatechannel == 1 %}selected{% endif %}>{{_('Stable (Automatic)')}}</option-->
|
||||||
<option value="2" {% if config.config_updatechannel == 2 %}selected{% endif %}>{{_('Nightly')}}</option>
|
<option value="2" {% if config.config_updatechannel == 2 %}selected{% endif %}>{{_('Nightly')}}</option>
|
||||||
@ -327,10 +327,10 @@
|
|||||||
|
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<button type="submit" name="submit" class="btn btn-default">{{_('Submit')}}</button>
|
<button type="submit" name="submit" class="btn btn-default">{{_('Submit')}}</button>
|
||||||
{% if not origin %}
|
{% if show_back_button %}
|
||||||
<a href="{{ url_for('admin.admin') }}" class="btn btn-default">{{_('Back')}}</a>
|
<a href="{{ url_for('admin.admin') }}" class="btn btn-default">{{_('Back')}}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if success %}
|
{% if show_login_button %}
|
||||||
<a href="{{ url_for('web.login') }}" name="login" class="btn btn-default">{{_('Login')}}</a>
|
<a href="{{ url_for('web.login') }}" name="login" class="btn btn-default">{{_('Login')}}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<a class="navbar-brand" href="{{url_for('web.index')}}">{{instance}}</a>
|
<a class="navbar-brand" href="{{url_for('web.index')}}">{{instance}}</a>
|
||||||
</div>
|
</div>
|
||||||
{% if g.user.is_authenticated or g.user.is_anonymous %}
|
{% if g.user.is_authenticated or g.allow_anonymous %}
|
||||||
<form class="navbar-form navbar-left" role="search" action="{{url_for('web.search')}}" method="GET">
|
<form class="navbar-form navbar-left" role="search" action="{{url_for('web.search')}}" method="GET">
|
||||||
<div class="form-group input-group input-group-sm">
|
<div class="form-group input-group input-group-sm">
|
||||||
<label for="query" class="sr-only">{{_('Search')}}</label>
|
<label for="query" class="sr-only">{{_('Search')}}</label>
|
||||||
@ -50,13 +50,13 @@
|
|||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="navbar-collapse collapse">
|
<div class="navbar-collapse collapse">
|
||||||
{% if g.user.is_authenticated or g.user.is_anonymous %}
|
{% if g.user.is_authenticated or g.allow_anonymous %}
|
||||||
<ul class="nav navbar-nav ">
|
<ul class="nav navbar-nav ">
|
||||||
<li><a href="{{url_for('web.advanced_search')}}"><span class="glyphicon glyphicon-search"></span><span class="hidden-sm"> {{_('Advanced Search')}}</span></a></li>
|
<li><a href="{{url_for('web.advanced_search')}}"><span class="glyphicon glyphicon-search"></span><span class="hidden-sm"> {{_('Advanced Search')}}</span></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<ul class="nav navbar-nav navbar-right" id="main-nav">
|
<ul class="nav navbar-nav navbar-right" id="main-nav">
|
||||||
{% if g.user.is_authenticated or g.user.is_anonymous %}
|
{% if g.user.is_authenticated or g.allow_anonymous %}
|
||||||
{% if g.user.role_upload() or g.user.role_admin()%}
|
{% if g.user.role_upload() or g.user.role_admin()%}
|
||||||
{% if g.allow_upload %}
|
{% if g.allow_upload %}
|
||||||
<li>
|
<li>
|
||||||
@ -115,7 +115,7 @@
|
|||||||
{%endif%}
|
{%endif%}
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row-fluid">
|
<div class="row-fluid">
|
||||||
{% if g.user.is_authenticated or g.user.is_anonymous %}
|
{% if g.user.is_authenticated or g.allow_anonymous %}
|
||||||
<div class="col-sm-2">
|
<div class="col-sm-2">
|
||||||
<nav class="navigation">
|
<nav class="navigation">
|
||||||
<ul class="list-unstyled" id="scnd-nav" intent in-standard-append="nav.navigation" in-mobile-after="#main-nav" in-mobile-class="nav navbar-nav">
|
<ul class="list-unstyled" id="scnd-nav" intent in-standard-append="nav.navigation" in-mobile-after="#main-nav" in-mobile-class="nav navbar-nav">
|
||||||
@ -128,7 +128,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% if g.user.is_authenticated or g.user.is_anonymous %}
|
{% if g.user.is_authenticated or allow_anonymous %}
|
||||||
<li class="nav-head hidden-xs public-shelves">{{_('Public Shelves')}}</li>
|
<li class="nav-head hidden-xs public-shelves">{{_('Public Shelves')}}</li>
|
||||||
{% for shelf in g.public_shelfes %}
|
{% for shelf in g.public_shelfes %}
|
||||||
<li><a href="{{url_for('shelf.show_shelf', shelf_id=shelf.id)}}"><span class="glyphicon glyphicon-list public_shelf"></span>{{shelf.name|shortentitle(40)}}</a></li>
|
<li><a href="{{url_for('shelf.show_shelf', shelf_id=shelf.id)}}"><span class="glyphicon glyphicon-list public_shelf"></span>{{shelf.name|shortentitle(40)}}</a></li>
|
||||||
|
487
cps/ub.py
487
cps/ub.py
@ -19,15 +19,14 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from __future__ import division, print_function, unicode_literals
|
from __future__ import division, print_function, unicode_literals
|
||||||
import sys
|
|
||||||
import os
|
import os
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
|
||||||
from binascii import hexlify
|
from binascii import hexlify
|
||||||
|
|
||||||
from flask import g
|
from flask import g
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
from flask_login import AnonymousUserMixin
|
from flask_login import AnonymousUserMixin
|
||||||
|
from werkzeug.local import LocalProxy
|
||||||
try:
|
try:
|
||||||
from flask_dance.consumer.backend.sqla import OAuthConsumerMixin
|
from flask_dance.consumer.backend.sqla import OAuthConsumerMixin
|
||||||
oauth_support = True
|
oauth_support = True
|
||||||
@ -40,22 +39,18 @@ from sqlalchemy.orm import relationship, sessionmaker
|
|||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
from werkzeug.security import generate_password_hash
|
from werkzeug.security import generate_password_hash
|
||||||
|
|
||||||
from . import constants, logger, cli
|
from . import constants # , config
|
||||||
|
|
||||||
|
|
||||||
session = None
|
session = None
|
||||||
|
|
||||||
engine = create_engine(u'sqlite:///{0}'.format(cli.settingspath), echo=False)
|
|
||||||
Base = declarative_base()
|
Base = declarative_base()
|
||||||
|
|
||||||
|
|
||||||
def get_sidebar_config(kwargs=None):
|
def get_sidebar_config(kwargs=None):
|
||||||
kwargs = kwargs or []
|
kwargs = kwargs or []
|
||||||
if 'content' in kwargs:
|
if 'content' in kwargs:
|
||||||
if not isinstance(kwargs['content'], Settings):
|
content = kwargs['content']
|
||||||
content = not kwargs['content'].role_anonymous()
|
content = isinstance(content, (User,LocalProxy)) and not content.role_anonymous()
|
||||||
else:
|
|
||||||
content = False
|
|
||||||
else:
|
else:
|
||||||
content = 'conf' in kwargs
|
content = 'conf' in kwargs
|
||||||
sidebar = list()
|
sidebar = list()
|
||||||
@ -148,7 +143,7 @@ class UserBase:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_anonymous(self):
|
def is_anonymous(self):
|
||||||
return False
|
return self.role_anonymous()
|
||||||
|
|
||||||
def get_id(self):
|
def get_id(self):
|
||||||
return str(self.id)
|
return str(self.id)
|
||||||
@ -200,7 +195,6 @@ class Anonymous(AnonymousUserMixin, UserBase):
|
|||||||
|
|
||||||
def loadSettings(self):
|
def loadSettings(self):
|
||||||
data = session.query(User).filter(User.role.op('&')(constants.ROLE_ANONYMOUS) == constants.ROLE_ANONYMOUS).first() # type: User
|
data = session.query(User).filter(User.role.op('&')(constants.ROLE_ANONYMOUS) == constants.ROLE_ANONYMOUS).first() # type: User
|
||||||
settings = session.query(Settings).first()
|
|
||||||
self.nickname = data.nickname
|
self.nickname = data.nickname
|
||||||
self.role = data.role
|
self.role = data.role
|
||||||
self.id=data.id
|
self.id=data.id
|
||||||
@ -208,9 +202,11 @@ class Anonymous(AnonymousUserMixin, UserBase):
|
|||||||
self.default_language = data.default_language
|
self.default_language = data.default_language
|
||||||
self.locale = data.locale
|
self.locale = data.locale
|
||||||
self.mature_content = data.mature_content
|
self.mature_content = data.mature_content
|
||||||
self.anon_browse = settings.config_anonbrowse
|
|
||||||
self.kindle_mail = data.kindle_mail
|
self.kindle_mail = data.kindle_mail
|
||||||
|
|
||||||
|
# settings = session.query(config).first()
|
||||||
|
# self.anon_browse = settings.config_anonbrowse
|
||||||
|
|
||||||
def role_admin(self):
|
def role_admin(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -220,7 +216,7 @@ class Anonymous(AnonymousUserMixin, UserBase):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_anonymous(self):
|
def is_anonymous(self):
|
||||||
return self.anon_browse
|
return True # self.anon_browse
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_authenticated(self):
|
def is_authenticated(self):
|
||||||
@ -295,78 +291,6 @@ class Registration(Base):
|
|||||||
return u"<Registration('{0}')>".format(self.domain)
|
return u"<Registration('{0}')>".format(self.domain)
|
||||||
|
|
||||||
|
|
||||||
# Baseclass for representing settings in app.db with email server settings and Calibre database settings
|
|
||||||
# (application settings)
|
|
||||||
class Settings(Base):
|
|
||||||
__tablename__ = 'settings'
|
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
|
||||||
mail_server = Column(String)
|
|
||||||
mail_port = Column(Integer, default=25)
|
|
||||||
mail_use_ssl = Column(SmallInteger, default=0)
|
|
||||||
mail_login = Column(String)
|
|
||||||
mail_password = Column(String)
|
|
||||||
mail_from = Column(String)
|
|
||||||
config_calibre_dir = Column(String)
|
|
||||||
config_port = Column(Integer, default=constants.DEFAULT_PORT)
|
|
||||||
config_certfile = Column(String)
|
|
||||||
config_keyfile = Column(String)
|
|
||||||
config_calibre_web_title = Column(String, default=u'Calibre-Web')
|
|
||||||
config_books_per_page = Column(Integer, default=60)
|
|
||||||
config_random_books = Column(Integer, default=4)
|
|
||||||
config_authors_max = Column(Integer, default=0)
|
|
||||||
config_read_column = Column(Integer, default=0)
|
|
||||||
config_title_regex = Column(String, default=u'^(A|The|An|Der|Die|Das|Den|Ein|Eine|Einen|Dem|Des|Einem|Eines)\s+')
|
|
||||||
config_log_level = Column(SmallInteger, default=logger.DEFAULT_LOG_LEVEL)
|
|
||||||
config_access_log = Column(SmallInteger, default=0)
|
|
||||||
config_uploading = Column(SmallInteger, default=0)
|
|
||||||
config_anonbrowse = Column(SmallInteger, default=0)
|
|
||||||
config_public_reg = Column(SmallInteger, default=0)
|
|
||||||
config_default_role = Column(SmallInteger, default=0)
|
|
||||||
config_default_show = Column(SmallInteger, default=6143)
|
|
||||||
config_columns_to_ignore = Column(String)
|
|
||||||
config_use_google_drive = Column(Boolean)
|
|
||||||
config_google_drive_folder = Column(String)
|
|
||||||
config_google_drive_watch_changes_response = Column(String)
|
|
||||||
config_remote_login = Column(Boolean)
|
|
||||||
config_use_goodreads = Column(Boolean)
|
|
||||||
config_goodreads_api_key = Column(String)
|
|
||||||
config_goodreads_api_secret = Column(String)
|
|
||||||
config_login_type = Column(Integer, default=0)
|
|
||||||
# config_use_ldap = Column(Boolean)
|
|
||||||
config_ldap_provider_url = Column(String)
|
|
||||||
config_ldap_dn = Column(String)
|
|
||||||
# config_use_github_oauth = Column(Boolean)
|
|
||||||
config_github_oauth_client_id = Column(String)
|
|
||||||
config_github_oauth_client_secret = Column(String)
|
|
||||||
# config_use_google_oauth = Column(Boolean)
|
|
||||||
config_google_oauth_client_id = Column(String)
|
|
||||||
config_google_oauth_client_secret = Column(String)
|
|
||||||
config_ldap_provider_url = Column(String, default='localhost')
|
|
||||||
config_ldap_port = Column(SmallInteger, default=389)
|
|
||||||
config_ldap_schema = Column(String, default='ldap')
|
|
||||||
config_ldap_serv_username = Column(String)
|
|
||||||
config_ldap_serv_password = Column(String)
|
|
||||||
config_ldap_use_ssl = Column(Boolean, default=False)
|
|
||||||
config_ldap_use_tls = Column(Boolean, default=False)
|
|
||||||
config_ldap_require_cert = Column(Boolean, default=False)
|
|
||||||
config_ldap_cert_path = Column(String)
|
|
||||||
config_ldap_dn = Column(String)
|
|
||||||
config_ldap_user_object = Column(String)
|
|
||||||
config_ldap_openldap = Column(Boolean)
|
|
||||||
config_mature_content_tags = Column(String)
|
|
||||||
config_logfile = Column(String)
|
|
||||||
config_access_logfile = Column(String)
|
|
||||||
config_ebookconverter = Column(Integer, default=0)
|
|
||||||
config_converterpath = Column(String)
|
|
||||||
config_calibre = Column(String)
|
|
||||||
config_rarfile_location = Column(String)
|
|
||||||
config_theme = Column(Integer, default=0)
|
|
||||||
config_updatechannel = Column(Integer, default=0)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class RemoteAuthToken(Base):
|
class RemoteAuthToken(Base):
|
||||||
__tablename__ = 'remote_auth_token'
|
__tablename__ = 'remote_auth_token'
|
||||||
@ -385,153 +309,11 @@ class RemoteAuthToken(Base):
|
|||||||
return '<Token %r>' % self.id
|
return '<Token %r>' % self.id
|
||||||
|
|
||||||
|
|
||||||
# Class holds all application specific settings in calibre-web
|
|
||||||
class Config:
|
|
||||||
def __init__(self):
|
|
||||||
self.db_configured = None
|
|
||||||
self.config_logfile = None
|
|
||||||
self.loadSettings()
|
|
||||||
|
|
||||||
def loadSettings(self):
|
|
||||||
data = session.query(Settings).first() # type: Settings
|
|
||||||
|
|
||||||
self.config_calibre_dir = data.config_calibre_dir
|
|
||||||
self.config_port = data.config_port
|
|
||||||
self.config_certfile = data.config_certfile
|
|
||||||
self.config_keyfile = data.config_keyfile
|
|
||||||
self.config_calibre_web_title = data.config_calibre_web_title
|
|
||||||
self.config_books_per_page = data.config_books_per_page
|
|
||||||
self.config_random_books = data.config_random_books
|
|
||||||
self.config_authors_max = data.config_authors_max
|
|
||||||
self.config_title_regex = data.config_title_regex
|
|
||||||
self.config_read_column = data.config_read_column
|
|
||||||
self.config_log_level = data.config_log_level
|
|
||||||
self.config_access_log = data.config_access_log
|
|
||||||
self.config_uploading = data.config_uploading
|
|
||||||
self.config_anonbrowse = data.config_anonbrowse
|
|
||||||
self.config_public_reg = data.config_public_reg
|
|
||||||
self.config_default_role = data.config_default_role
|
|
||||||
self.config_default_show = data.config_default_show
|
|
||||||
self.config_columns_to_ignore = data.config_columns_to_ignore
|
|
||||||
self.config_use_google_drive = data.config_use_google_drive
|
|
||||||
self.config_google_drive_folder = data.config_google_drive_folder
|
|
||||||
self.config_ebookconverter = data.config_ebookconverter
|
|
||||||
self.config_converterpath = data.config_converterpath
|
|
||||||
self.config_calibre = data.config_calibre
|
|
||||||
if data.config_google_drive_watch_changes_response:
|
|
||||||
self.config_google_drive_watch_changes_response = json.loads(data.config_google_drive_watch_changes_response)
|
|
||||||
else:
|
|
||||||
self.config_google_drive_watch_changes_response=None
|
|
||||||
self.config_columns_to_ignore = data.config_columns_to_ignore
|
|
||||||
self.db_configured = bool(self.config_calibre_dir is not None and
|
|
||||||
(not self.config_use_google_drive or os.path.exists(self.config_calibre_dir + '/metadata.db')))
|
|
||||||
self.config_remote_login = data.config_remote_login
|
|
||||||
self.config_use_goodreads = data.config_use_goodreads
|
|
||||||
self.config_goodreads_api_key = data.config_goodreads_api_key
|
|
||||||
self.config_goodreads_api_secret = data.config_goodreads_api_secret
|
|
||||||
self.config_login_type = data.config_login_type
|
|
||||||
# self.config_use_ldap = data.config_use_ldap
|
|
||||||
self.config_ldap_user_object = data.config_ldap_user_object
|
|
||||||
self.config_ldap_openldap = data.config_ldap_openldap
|
|
||||||
self.config_ldap_provider_url = data.config_ldap_provider_url
|
|
||||||
self.config_ldap_port = data.config_ldap_port
|
|
||||||
self.config_ldap_schema = data.config_ldap_schema
|
|
||||||
self.config_ldap_serv_username = data.config_ldap_serv_username
|
|
||||||
self.config_ldap_serv_password = data.config_ldap_serv_password
|
|
||||||
self.config_ldap_use_ssl = data.config_ldap_use_ssl
|
|
||||||
self.config_ldap_use_tls = data.config_ldap_use_ssl
|
|
||||||
self.config_ldap_require_cert = data.config_ldap_require_cert
|
|
||||||
self.config_ldap_cert_path = data.config_ldap_cert_path
|
|
||||||
self.config_ldap_dn = data.config_ldap_dn
|
|
||||||
# self.config_use_github_oauth = data.config_use_github_oauth
|
|
||||||
self.config_github_oauth_client_id = data.config_github_oauth_client_id
|
|
||||||
self.config_github_oauth_client_secret = data.config_github_oauth_client_secret
|
|
||||||
# self.config_use_google_oauth = data.config_use_google_oauth
|
|
||||||
self.config_google_oauth_client_id = data.config_google_oauth_client_id
|
|
||||||
self.config_google_oauth_client_secret = data.config_google_oauth_client_secret
|
|
||||||
self.config_mature_content_tags = data.config_mature_content_tags or u''
|
|
||||||
self.config_logfile = data.config_logfile or u''
|
|
||||||
self.config_access_logfile = data.config_access_logfile or u''
|
|
||||||
self.config_rarfile_location = data.config_rarfile_location
|
|
||||||
self.config_theme = data.config_theme
|
|
||||||
self.config_updatechannel = data.config_updatechannel
|
|
||||||
logger.setup(self.config_logfile, self.config_log_level)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def get_update_channel(self):
|
|
||||||
return self.config_updatechannel
|
|
||||||
|
|
||||||
def get_config_certfile(self):
|
|
||||||
if cli.certfilepath:
|
|
||||||
return cli.certfilepath
|
|
||||||
if cli.certfilepath is "":
|
|
||||||
return None
|
|
||||||
return self.config_certfile
|
|
||||||
|
|
||||||
def get_config_keyfile(self):
|
|
||||||
if cli.keyfilepath:
|
|
||||||
return cli.keyfilepath
|
|
||||||
if cli.certfilepath is "":
|
|
||||||
return None
|
|
||||||
return self.config_keyfile
|
|
||||||
|
|
||||||
def get_config_ipaddress(self):
|
|
||||||
return cli.ipadress or ""
|
|
||||||
|
|
||||||
def get_ipaddress_type(self):
|
|
||||||
return cli.ipv6
|
|
||||||
|
|
||||||
def _has_role(self, role_flag):
|
|
||||||
return constants.has_flag(self.config_default_role, role_flag)
|
|
||||||
|
|
||||||
def role_admin(self):
|
|
||||||
return self._has_role(constants.ROLE_ADMIN)
|
|
||||||
|
|
||||||
def role_download(self):
|
|
||||||
return self._has_role(constants.ROLE_DOWNLOAD)
|
|
||||||
|
|
||||||
def role_viewer(self):
|
|
||||||
return self._has_role(constants.ROLE_VIEWER)
|
|
||||||
|
|
||||||
def role_upload(self):
|
|
||||||
return self._has_role(constants.ROLE_UPLOAD)
|
|
||||||
|
|
||||||
def role_edit(self):
|
|
||||||
return self._has_role(constants.ROLE_EDIT)
|
|
||||||
|
|
||||||
def role_passwd(self):
|
|
||||||
return self._has_role(constants.ROLE_PASSWD)
|
|
||||||
|
|
||||||
def role_edit_shelfs(self):
|
|
||||||
return self._has_role(constants.ROLE_EDIT_SHELFS)
|
|
||||||
|
|
||||||
def role_delete_books(self):
|
|
||||||
return self._has_role(constants.ROLE_DELETE_BOOKS)
|
|
||||||
|
|
||||||
def show_element_new_user(self, value):
|
|
||||||
return constants.has_flag(self.config_default_show, value)
|
|
||||||
|
|
||||||
def show_detail_random(self):
|
|
||||||
return self.show_element_new_user(constants.DETAIL_RANDOM)
|
|
||||||
|
|
||||||
def show_mature_content(self):
|
|
||||||
return self.show_element_new_user(constants.MATURE_CONTENT)
|
|
||||||
|
|
||||||
def mature_content_tags(self):
|
|
||||||
if sys.version_info > (3, 0): # Python3 str, Python2 unicode
|
|
||||||
lstrip = str.lstrip
|
|
||||||
else:
|
|
||||||
lstrip = unicode.lstrip
|
|
||||||
return list(map(lstrip, self.config_mature_content_tags.split(",")))
|
|
||||||
|
|
||||||
def get_Log_Level(self):
|
|
||||||
return logger.get_level_name(self.config_log_level)
|
|
||||||
|
|
||||||
|
|
||||||
# Migrate database to current version, has to be updated after every database change. Currently migration from
|
# Migrate database to current version, has to be updated after every database change. Currently migration from
|
||||||
# everywhere to curent should work. Migration is done by checking if relevant coloums are existing, and than adding
|
# everywhere to curent should work. Migration is done by checking if relevant coloums are existing, and than adding
|
||||||
# rows with SQL commands
|
# rows with SQL commands
|
||||||
def migrate_Database():
|
def migrate_Database(session):
|
||||||
|
engine = session.bind
|
||||||
if not engine.dialect.has_table(engine.connect(), "book_read_link"):
|
if not engine.dialect.has_table(engine.connect(), "book_read_link"):
|
||||||
ReadBook.__table__.create(bind=engine)
|
ReadBook.__table__.create(bind=engine)
|
||||||
if not engine.dialect.has_table(engine.connect(), "bookmark"):
|
if not engine.dialect.has_table(engine.connect(), "bookmark"):
|
||||||
@ -547,45 +329,12 @@ def migrate_Database():
|
|||||||
conn = engine.connect()
|
conn = engine.connect()
|
||||||
conn.execute("insert into registration (domain) values('%.%')")
|
conn.execute("insert into registration (domain) values('%.%')")
|
||||||
session.commit()
|
session.commit()
|
||||||
try:
|
|
||||||
session.query(exists().where(Settings.config_use_google_drive)).scalar()
|
|
||||||
except exc.OperationalError:
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_use_google_drive` INTEGER DEFAULT 0")
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_google_drive_folder` String DEFAULT ''")
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_google_drive_watch_changes_response` String DEFAULT ''")
|
|
||||||
session.commit()
|
|
||||||
try:
|
|
||||||
session.query(exists().where(Settings.config_columns_to_ignore)).scalar()
|
|
||||||
except exc.OperationalError:
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_columns_to_ignore` String DEFAULT ''")
|
|
||||||
session.commit()
|
|
||||||
try:
|
|
||||||
session.query(exists().where(Settings.config_default_role)).scalar()
|
|
||||||
except exc.OperationalError: # Database is not compatible, some rows are missing
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_default_role` SmallInteger DEFAULT 0")
|
|
||||||
session.commit()
|
|
||||||
try:
|
|
||||||
session.query(exists().where(Settings.config_authors_max)).scalar()
|
|
||||||
except exc.OperationalError: # Database is not compatible, some rows are missing
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_authors_max` INTEGER DEFAULT 0")
|
|
||||||
session.commit()
|
|
||||||
try:
|
try:
|
||||||
session.query(exists().where(BookShelf.order)).scalar()
|
session.query(exists().where(BookShelf.order)).scalar()
|
||||||
except exc.OperationalError: # Database is not compatible, some rows are missing
|
except exc.OperationalError: # Database is not compatible, some rows are missing
|
||||||
conn = engine.connect()
|
conn = engine.connect()
|
||||||
conn.execute("ALTER TABLE book_shelf_link ADD column 'order' INTEGER DEFAULT 1")
|
conn.execute("ALTER TABLE book_shelf_link ADD column 'order' INTEGER DEFAULT 1")
|
||||||
session.commit()
|
session.commit()
|
||||||
try:
|
|
||||||
session.query(exists().where(Settings.config_rarfile_location)).scalar()
|
|
||||||
session.commit()
|
|
||||||
except exc.OperationalError: # Database is not compatible, some rows are missing
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_rarfile_location` String DEFAULT ''")
|
|
||||||
session.commit()
|
|
||||||
try:
|
try:
|
||||||
create = False
|
create = False
|
||||||
session.query(exists().where(User.sidebar_view)).scalar()
|
session.query(exists().where(User.sidebar_view)).scalar()
|
||||||
@ -617,146 +366,7 @@ def migrate_Database():
|
|||||||
conn.execute("ALTER TABLE user ADD column `mature_content` INTEGER DEFAULT 1")
|
conn.execute("ALTER TABLE user ADD column `mature_content` INTEGER DEFAULT 1")
|
||||||
|
|
||||||
if session.query(User).filter(User.role.op('&')(constants.ROLE_ANONYMOUS) == constants.ROLE_ANONYMOUS).first() is None:
|
if session.query(User).filter(User.role.op('&')(constants.ROLE_ANONYMOUS) == constants.ROLE_ANONYMOUS).first() is None:
|
||||||
create_anonymous_user()
|
create_anonymous_user(session)
|
||||||
try:
|
|
||||||
session.query(exists().where(Settings.config_remote_login)).scalar()
|
|
||||||
except exc.OperationalError:
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_remote_login` INTEGER DEFAULT 0")
|
|
||||||
try:
|
|
||||||
session.query(exists().where(Settings.config_use_goodreads)).scalar()
|
|
||||||
except exc.OperationalError:
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_use_goodreads` INTEGER DEFAULT 0")
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_goodreads_api_key` String DEFAULT ''")
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_goodreads_api_secret` String DEFAULT ''")
|
|
||||||
try:
|
|
||||||
session.query(exists().where(Settings.config_mature_content_tags)).scalar()
|
|
||||||
except exc.OperationalError:
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_mature_content_tags` String DEFAULT ''")
|
|
||||||
try:
|
|
||||||
session.query(exists().where(Settings.config_default_show)).scalar()
|
|
||||||
except exc.OperationalError: # Database is not compatible, some rows are missing
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_default_show` SmallInteger DEFAULT 2047")
|
|
||||||
session.commit()
|
|
||||||
try:
|
|
||||||
session.query(exists().where(Settings.config_logfile)).scalar()
|
|
||||||
except exc.OperationalError: # Database is not compatible, some rows are missing
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_logfile` String DEFAULT ''")
|
|
||||||
session.commit()
|
|
||||||
try:
|
|
||||||
session.query(exists().where(Settings.config_certfile)).scalar()
|
|
||||||
except exc.OperationalError: # Database is not compatible, some rows are missing
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_certfile` String DEFAULT ''")
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_keyfile` String DEFAULT ''")
|
|
||||||
session.commit()
|
|
||||||
try:
|
|
||||||
session.query(exists().where(Settings.config_read_column)).scalar()
|
|
||||||
except exc.OperationalError: # Database is not compatible, some rows are missing
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_read_column` INTEGER DEFAULT 0")
|
|
||||||
session.commit()
|
|
||||||
try:
|
|
||||||
session.query(exists().where(Settings.config_ebookconverter)).scalar()
|
|
||||||
except exc.OperationalError: # Database is not compatible, some rows are missing
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_ebookconverter` INTEGER DEFAULT 0")
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_converterpath` String DEFAULT ''")
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_calibre` String DEFAULT ''")
|
|
||||||
session.commit()
|
|
||||||
try:
|
|
||||||
session.query(exists().where(Settings.config_login_type)).scalar()
|
|
||||||
except exc.OperationalError:
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_login_type` INTEGER DEFAULT 0")
|
|
||||||
session.commit()
|
|
||||||
try:
|
|
||||||
session.query(exists().where(Settings.config_ldap_provider_url)).scalar()
|
|
||||||
except exc.OperationalError:
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_ldap_provider_url` String DEFAULT ''")
|
|
||||||
session.commit()
|
|
||||||
try:
|
|
||||||
session.query(exists().where(Settings.config_ldap_port)).scalar()
|
|
||||||
except exc.OperationalError:
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_ldap_port` INTEGER DEFAULT ''")
|
|
||||||
session.commit()
|
|
||||||
try:
|
|
||||||
session.query(exists().where(Settings.config_ldap_schema)).scalar()
|
|
||||||
except exc.OperationalError:
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_ldap_schema` String DEFAULT ''")
|
|
||||||
session.commit()
|
|
||||||
try:
|
|
||||||
session.query(exists().where(Settings.config_ldap_serv_username)).scalar()
|
|
||||||
except exc.OperationalError:
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_ldap_serv_username` String DEFAULT ''")
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_ldap_serv_password` String DEFAULT ''")
|
|
||||||
session.commit()
|
|
||||||
try:
|
|
||||||
session.query(exists().where(Settings.config_ldap_use_ssl)).scalar()
|
|
||||||
except exc.OperationalError:
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_ldap_use_ssl` INTEGER DEFAULT 0")
|
|
||||||
session.commit()
|
|
||||||
try:
|
|
||||||
session.query(exists().where(Settings.config_ldap_use_tls)).scalar()
|
|
||||||
except exc.OperationalError:
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_ldap_use_tls` INTEGER DEFAULT 0")
|
|
||||||
session.commit()
|
|
||||||
try:
|
|
||||||
session.query(exists().where(Settings.config_ldap_require_cert)).scalar()
|
|
||||||
except exc.OperationalError:
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_ldap_require_cert` INTEGER DEFAULT 0")
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_ldap_cert_path` String DEFAULT ''")
|
|
||||||
session.commit()
|
|
||||||
try:
|
|
||||||
session.query(exists().where(Settings.config_ldap_dn)).scalar()
|
|
||||||
except exc.OperationalError:
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_ldap_dn` String DEFAULT ''")
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_github_oauth_client_id` String DEFAULT ''")
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_github_oauth_client_secret` String DEFAULT ''")
|
|
||||||
session.commit()
|
|
||||||
try:
|
|
||||||
session.query(exists().where(Settings.config_ldap_user_object)).scalar()
|
|
||||||
except exc.OperationalError:
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_ldap_user_object` String DEFAULT ''")
|
|
||||||
session.commit()
|
|
||||||
try:
|
|
||||||
session.query(exists().where(Settings.config_ldap_openldap)).scalar()
|
|
||||||
except exc.OperationalError:
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_ldap_openldap` INTEGER DEFAULT 0")
|
|
||||||
session.commit()
|
|
||||||
try:
|
|
||||||
session.query(exists().where(Settings.config_theme)).scalar()
|
|
||||||
except exc.OperationalError: # Database is not compatible, some rows are missing
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_theme` INTEGER DEFAULT 0")
|
|
||||||
session.commit()
|
|
||||||
try:
|
|
||||||
session.query(exists().where(Settings.config_updatechannel)).scalar()
|
|
||||||
except exc.OperationalError: # Database is not compatible, some rows are missing
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_updatechannel` INTEGER DEFAULT 0")
|
|
||||||
session.commit()
|
|
||||||
try:
|
|
||||||
session.query(exists().where(Settings.config_access_log)).scalar()
|
|
||||||
except exc.OperationalError: # Database is not compatible, some rows are missing
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_access_log` INTEGER DEFAULT 0")
|
|
||||||
conn.execute("ALTER TABLE Settings ADD column `config_access_logfile` String DEFAULT ''")
|
|
||||||
session.commit()
|
|
||||||
try:
|
try:
|
||||||
# check if one table with autoincrement is existing (should be user table)
|
# check if one table with autoincrement is existing (should be user table)
|
||||||
conn = engine.connect()
|
conn = engine.connect()
|
||||||
@ -792,42 +402,13 @@ def migrate_Database():
|
|||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
def clean_database():
|
def clean_database(session):
|
||||||
# Remove expired remote login tokens
|
# Remove expired remote login tokens
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
session.query(RemoteAuthToken).filter(now > RemoteAuthToken.expiration).delete()
|
session.query(RemoteAuthToken).filter(now > RemoteAuthToken.expiration).delete()
|
||||||
|
|
||||||
|
|
||||||
def create_default_config():
|
|
||||||
settings = Settings()
|
|
||||||
settings.mail_server = "mail.example.com"
|
|
||||||
settings.mail_port = 25
|
|
||||||
settings.mail_use_ssl = 0
|
|
||||||
settings.mail_login = "mail@example.com"
|
|
||||||
settings.mail_password = "mypassword"
|
|
||||||
settings.mail_from = "automailer <mail@example.com>"
|
|
||||||
|
|
||||||
session.add(settings)
|
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
def get_mail_settings():
|
|
||||||
settings = session.query(Settings).first()
|
|
||||||
|
|
||||||
if not settings:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
data = {
|
|
||||||
'mail_server': settings.mail_server,
|
|
||||||
'mail_port': settings.mail_port,
|
|
||||||
'mail_use_ssl': settings.mail_use_ssl,
|
|
||||||
'mail_login': settings.mail_login,
|
|
||||||
'mail_password': settings.mail_password,
|
|
||||||
'mail_from': settings.mail_from
|
|
||||||
}
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
# Save downloaded books per user in calibre-web's own database
|
# Save downloaded books per user in calibre-web's own database
|
||||||
def update_download(book_id, user_id):
|
def update_download(book_id, user_id):
|
||||||
check = session.query(Downloads).filter(Downloads.user_id == user_id).filter(Downloads.book_id ==
|
check = session.query(Downloads).filter(Downloads.user_id == user_id).filter(Downloads.book_id ==
|
||||||
@ -844,7 +425,7 @@ def delete_download(book_id):
|
|||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
# Generate user Guest (translated text), as anoymous user, no rights
|
# Generate user Guest (translated text), as anoymous user, no rights
|
||||||
def create_anonymous_user():
|
def create_anonymous_user(session):
|
||||||
user = User()
|
user = User()
|
||||||
user.nickname = "Guest"
|
user.nickname = "Guest"
|
||||||
user.email = 'no@email'
|
user.email = 'no@email'
|
||||||
@ -854,12 +435,12 @@ def create_anonymous_user():
|
|||||||
session.add(user)
|
session.add(user)
|
||||||
try:
|
try:
|
||||||
session.commit()
|
session.commit()
|
||||||
except Exception:
|
except Exception as e:
|
||||||
session.rollback()
|
session.rollback()
|
||||||
|
|
||||||
|
|
||||||
# Generate User admin with admin123 password, and access to everything
|
# Generate User admin with admin123 password, and access to everything
|
||||||
def create_admin_user():
|
def create_admin_user(session):
|
||||||
user = User()
|
user = User()
|
||||||
user.nickname = "admin"
|
user.nickname = "admin"
|
||||||
user.role = constants.ADMIN_USER_ROLES
|
user.role = constants.ADMIN_USER_ROLES
|
||||||
@ -873,23 +454,37 @@ def create_admin_user():
|
|||||||
except Exception:
|
except Exception:
|
||||||
session.rollback()
|
session.rollback()
|
||||||
|
|
||||||
def init_db():
|
|
||||||
|
def init_db(app_db_path):
|
||||||
# Open session for database connection
|
# Open session for database connection
|
||||||
global session
|
global session
|
||||||
|
|
||||||
|
engine = create_engine(u'sqlite:///{0}'.format(app_db_path), echo=False)
|
||||||
|
|
||||||
Session = sessionmaker()
|
Session = sessionmaker()
|
||||||
Session.configure(bind=engine)
|
Session.configure(bind=engine)
|
||||||
session = Session()
|
session = Session()
|
||||||
|
|
||||||
|
if os.path.exists(app_db_path):
|
||||||
if not os.path.exists(cli.settingspath):
|
|
||||||
try:
|
|
||||||
Base.metadata.create_all(engine)
|
Base.metadata.create_all(engine)
|
||||||
create_default_config()
|
migrate_Database(session)
|
||||||
create_admin_user()
|
clean_database(session)
|
||||||
create_anonymous_user()
|
|
||||||
except Exception:
|
|
||||||
raise
|
|
||||||
else:
|
else:
|
||||||
Base.metadata.create_all(engine)
|
Base.metadata.create_all(engine)
|
||||||
migrate_Database()
|
create_admin_user(session)
|
||||||
clean_database()
|
create_anonymous_user(session)
|
||||||
|
|
||||||
|
|
||||||
|
def dispose():
|
||||||
|
global session
|
||||||
|
|
||||||
|
engine = None
|
||||||
|
if session:
|
||||||
|
engine = session.bind
|
||||||
|
try: session.close()
|
||||||
|
except: pass
|
||||||
|
session = None
|
||||||
|
|
||||||
|
if engine:
|
||||||
|
try: engine.dispose()
|
||||||
|
except: pass
|
||||||
|
@ -22,7 +22,6 @@ import sys
|
|||||||
import os
|
import os
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
import requests
|
|
||||||
import shutil
|
import shutil
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
@ -30,6 +29,7 @@ import zipfile
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from tempfile import gettempdir
|
from tempfile import gettempdir
|
||||||
|
|
||||||
|
import requests
|
||||||
from babel.dates import format_datetime
|
from babel.dates import format_datetime
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
|
|
||||||
@ -58,15 +58,13 @@ class Updater(threading.Thread):
|
|||||||
self.updateIndex = None
|
self.updateIndex = None
|
||||||
|
|
||||||
def get_current_version_info(self):
|
def get_current_version_info(self):
|
||||||
if config.get_update_channel == constants.UPDATE_STABLE:
|
if config.config_updatechannel == constants.UPDATE_STABLE:
|
||||||
return self._stable_version_info()
|
return self._stable_version_info()
|
||||||
else:
|
|
||||||
return self._nightly_version_info()
|
return self._nightly_version_info()
|
||||||
|
|
||||||
def get_available_updates(self, request_method):
|
def get_available_updates(self, request_method):
|
||||||
if config.get_update_channel == constants.UPDATE_STABLE:
|
if config.config_updatechannel == constants.UPDATE_STABLE:
|
||||||
return self._stable_available_updates(request_method)
|
return self._stable_available_updates(request_method)
|
||||||
else:
|
|
||||||
return self._nightly_available_updates(request_method)
|
return self._nightly_available_updates(request_method)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
@ -430,9 +428,8 @@ class Updater(threading.Thread):
|
|||||||
return json.dumps(status)
|
return json.dumps(status)
|
||||||
|
|
||||||
def _get_request_path(self):
|
def _get_request_path(self):
|
||||||
if config.get_update_channel == constants.UPDATE_STABLE:
|
if config.config_updatechannel == constants.UPDATE_STABLE:
|
||||||
return self.updateFile
|
return self.updateFile
|
||||||
else:
|
|
||||||
return _REPOSITORY_API_URL + '/zipball/master'
|
return _REPOSITORY_API_URL + '/zipball/master'
|
||||||
|
|
||||||
def _load_remote_data(self, repository_url):
|
def _load_remote_data(self, repository_url):
|
||||||
|
83
cps/web.py
83
cps/web.py
@ -41,18 +41,20 @@ from werkzeug.exceptions import default_exceptions
|
|||||||
from werkzeug.datastructures import Headers
|
from werkzeug.datastructures import Headers
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
|
|
||||||
from . import constants, logger, isoLanguages, ldap1
|
from . import constants, logger, isoLanguages, services
|
||||||
from . import global_WorkerThread, searched_ids, lm, babel, db, ub, config, get_locale, app, language_table
|
from . import global_WorkerThread, searched_ids, lm, babel, db, ub, config, negociate_locale, get_locale, app
|
||||||
from .gdriveutils import getFileFromEbooksFolder, do_gdrive_download
|
from .gdriveutils import getFileFromEbooksFolder, do_gdrive_download
|
||||||
from .helper import common_filters, get_search_results, fill_indexpage, speaking_language, check_valid_domain, \
|
from .helper import common_filters, get_search_results, fill_indexpage, speaking_language, check_valid_domain, \
|
||||||
order_authors, get_typeahead, render_task_status, json_serial, get_unique_other_books, get_cc_columns, \
|
order_authors, get_typeahead, render_task_status, json_serial, get_cc_columns, \
|
||||||
get_book_cover, get_download_link, send_mail, generate_random_password, send_registration_mail, \
|
get_book_cover, get_download_link, send_mail, generate_random_password, send_registration_mail, \
|
||||||
check_send_to_kindle, check_read_formats, lcase
|
check_send_to_kindle, check_read_formats, lcase
|
||||||
from .pagination import Pagination
|
from .pagination import Pagination
|
||||||
from .redirect import redirect_back
|
from .redirect import redirect_back
|
||||||
|
|
||||||
feature_support = dict()
|
feature_support = {
|
||||||
feature_support['ldap'] = ldap1.ldap_supported()
|
'ldap': bool(services.ldap),
|
||||||
|
'goodreads': bool(services.goodreads)
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from .oauth_bb import oauth_check, register_user_with_oauth, logout_oauth_user, get_oauth_status
|
from .oauth_bb import oauth_check, register_user_with_oauth, logout_oauth_user, get_oauth_status
|
||||||
@ -61,12 +63,6 @@ except ImportError:
|
|||||||
feature_support['oauth'] = False
|
feature_support['oauth'] = False
|
||||||
oauth_check = {}
|
oauth_check = {}
|
||||||
|
|
||||||
try:
|
|
||||||
from goodreads.client import GoodreadsClient
|
|
||||||
feature_support['goodreads'] = True
|
|
||||||
except ImportError:
|
|
||||||
feature_support['goodreads'] = False
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@ -230,8 +226,11 @@ def render_title_template(*args, **kwargs):
|
|||||||
|
|
||||||
@web.before_app_request
|
@web.before_app_request
|
||||||
def before_request():
|
def before_request():
|
||||||
|
# log.debug("before_request: %s %s %r", request.method, request.path, getattr(request, 'locale', None))
|
||||||
|
request._locale = negociate_locale()
|
||||||
g.user = current_user
|
g.user = current_user
|
||||||
g.allow_registration = config.config_public_reg
|
g.allow_registration = config.config_public_reg
|
||||||
|
g.allow_anonymous = config.config_anonbrowse
|
||||||
g.allow_upload = config.config_uploading
|
g.allow_upload = config.config_uploading
|
||||||
g.current_theme = config.config_theme
|
g.current_theme = config.config_theme
|
||||||
g.config_authors_max = config.config_authors_max
|
g.config_authors_max = config.config_authors_max
|
||||||
@ -292,7 +291,7 @@ def toggle_read(book_id):
|
|||||||
ub.session.commit()
|
ub.session.commit()
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort)
|
db.update_title_sort(config)
|
||||||
book = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
|
book = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
|
||||||
read_status = getattr(book, 'custom_column_' + str(config.config_read_column))
|
read_status = getattr(book, 'custom_column_' + str(config.config_read_column))
|
||||||
if len(read_status):
|
if len(read_status):
|
||||||
@ -396,10 +395,10 @@ def get_series_json():
|
|||||||
def get_languages_json():
|
def get_languages_json():
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
query = request.args.get('q').lower()
|
query = request.args.get('q').lower()
|
||||||
languages = language_table[get_locale()]
|
language_names = isoLanguages.get_language_names(get_locale())
|
||||||
entries_start = [s for key, s in languages.items() if s.lower().startswith(query.lower())]
|
entries_start = [s for key, s in language_names.items() if s.lower().startswith(query.lower())]
|
||||||
if len(entries_start) < 5:
|
if len(entries_start) < 5:
|
||||||
entries = [s for key, s in languages.items() if query in s.lower()]
|
entries = [s for key, s in language_names.items() if query in s.lower()]
|
||||||
entries_start.extend(entries[0:(5-len(entries_start))])
|
entries_start.extend(entries[0:(5-len(entries_start))])
|
||||||
entries_start = list(set(entries_start))
|
entries_start = list(set(entries_start))
|
||||||
json_dumps = json.dumps([dict(name=r) for r in entries_start[0:5]])
|
json_dumps = json.dumps([dict(name=r) for r in entries_start[0:5]])
|
||||||
@ -534,29 +533,26 @@ def render_hot_books(page):
|
|||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
|
|
||||||
def render_author_books(page, book_id, order):
|
|
||||||
entries, __, pagination = fill_indexpage(page, db.Books, db.Books.authors.any(db.Authors.id == book_id),
|
def render_author_books(page, author_id, order):
|
||||||
|
entries, __, pagination = fill_indexpage(page, db.Books, db.Books.authors.any(db.Authors.id == author_id),
|
||||||
[order[0], db.Series.name, db.Books.series_index],
|
[order[0], db.Series.name, db.Books.series_index],
|
||||||
db.books_series_link, db.Series)
|
db.books_series_link, db.Series)
|
||||||
if entries is None or not len(entries):
|
if entries is None or not len(entries):
|
||||||
flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
|
flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
|
||||||
return redirect(url_for("web.index"))
|
return redirect(url_for("web.index"))
|
||||||
|
|
||||||
name = db.session.query(db.Authors).filter(db.Authors.id == book_id).first().name.replace('|', ',')
|
author = db.session.query(db.Authors).get(author_id)
|
||||||
|
author_name = author.name.replace('|', ',')
|
||||||
|
|
||||||
author_info = None
|
author_info = None
|
||||||
other_books = []
|
other_books = []
|
||||||
if feature_support['goodreads'] and config.config_use_goodreads:
|
if services.goodreads and config.config_use_goodreads:
|
||||||
try:
|
author_info = services.goodreads.get_author_info(author_name)
|
||||||
gc = GoodreadsClient(config.config_goodreads_api_key, config.config_goodreads_api_secret)
|
other_books = services.goodreads.get_other_books(author_info, entries)
|
||||||
author_info = gc.find_author(author_name=name)
|
|
||||||
other_books = get_unique_other_books(entries.all(), author_info.books)
|
|
||||||
except Exception:
|
|
||||||
# Skip goodreads, if site is down/inaccessible
|
|
||||||
logger.error('Goodreads website is down/inaccessible')
|
|
||||||
|
|
||||||
return render_title_template('author.html', entries=entries, pagination=pagination, id=book_id,
|
return render_title_template('author.html', entries=entries, pagination=pagination, id=author_id,
|
||||||
title=_(u"Author: %(name)s", name=name), author=author_info, other_books=other_books,
|
title=_(u"Author: %(name)s", name=author_name), author=author_info, other_books=other_books,
|
||||||
page="author")
|
page="author")
|
||||||
|
|
||||||
|
|
||||||
@ -985,10 +981,7 @@ def serve_book(book_id, book_format):
|
|||||||
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()
|
headers = Headers()
|
||||||
try:
|
headers["Content-Type"] = mimetypes.types_map.get('.' + book_format, "application/octet-stream")
|
||||||
headers["Content-Type"] = mimetypes.types_map['.' + book_format]
|
|
||||||
except KeyError:
|
|
||||||
headers["Content-Type"] = "application/octet-stream"
|
|
||||||
df = getFileFromEbooksFolder(book.path, data.name + "." + book_format)
|
df = getFileFromEbooksFolder(book.path, data.name + "." + book_format)
|
||||||
return do_gdrive_download(df, headers)
|
return do_gdrive_download(df, headers)
|
||||||
else:
|
else:
|
||||||
@ -1007,7 +1000,7 @@ def download_link(book_id, book_format, anyname):
|
|||||||
@login_required
|
@login_required
|
||||||
@download_required
|
@download_required
|
||||||
def send_to_kindle(book_id, book_format, convert):
|
def send_to_kindle(book_id, book_format, convert):
|
||||||
settings = ub.get_mail_settings()
|
settings = config.get_mail_settings()
|
||||||
if settings.get("mail_server", "mail.example.com") == "mail.example.com":
|
if settings.get("mail_server", "mail.example.com") == "mail.example.com":
|
||||||
flash(_(u"Please configure the SMTP mail settings first..."), category="error")
|
flash(_(u"Please configure the SMTP mail settings first..."), category="error")
|
||||||
elif current_user.kindle_mail:
|
elif current_user.kindle_mail:
|
||||||
@ -1085,38 +1078,30 @@ def login():
|
|||||||
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 == 1 and not feature_support['ldap']:
|
if config.config_login_type == constants.LOGIN_LDAP and not services.ldap:
|
||||||
flash(_(u"Cannot activate LDAP authentication"), category="error")
|
flash(_(u"Cannot activate LDAP authentication"), category="error")
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
form = request.form.to_dict()
|
form = request.form.to_dict()
|
||||||
user = ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == form['username'].strip().lower())\
|
user = ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == form['username'].strip().lower())\
|
||||||
.first()
|
.first()
|
||||||
if config.config_login_type == 1 and user and feature_support['ldap']:
|
if config.config_login_type == constants.LOGIN_LDAP and services.ldap and user:
|
||||||
try:
|
login_result = services.ldap.bind_user(form['username'], form['password'])
|
||||||
if ldap1.ldap.bind_user(form['username'], form['password']) is not None:
|
if login_result:
|
||||||
login_user(user, remember=True)
|
login_user(user, remember=True)
|
||||||
flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.nickname),
|
flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.nickname),
|
||||||
category="success")
|
category="success")
|
||||||
return redirect_back(url_for("web.index"))
|
return redirect_back(url_for("web.index"))
|
||||||
except ldap1.ldap.INVALID_CREDENTIALS as e:
|
if login_result is None:
|
||||||
log.error('Login Error: ' + str(e))
|
flash(_(u"Could not login. LDAP server down, please contact your administrator"), category="error")
|
||||||
|
else:
|
||||||
ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr)
|
ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr)
|
||||||
log.info('LDAP Login failed for user "%s" IP-adress: %s', form['username'], ipAdress)
|
log.info('LDAP Login failed for user "%s" IP-adress: %s', form['username'], ipAdress)
|
||||||
flash(_(u"Wrong Username or Password"), category="error")
|
flash(_(u"Wrong Username or Password"), category="error")
|
||||||
except ldap1.ldap.SERVER_DOWN:
|
|
||||||
log.info('LDAP Login failed, LDAP Server down')
|
|
||||||
flash(_(u"Could not login. LDAP server down, please contact your administrator"), category="error")
|
|
||||||
'''except LDAPException as exception:
|
|
||||||
app.logger.error('Login Error: ' + str(exception))
|
|
||||||
ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr)
|
|
||||||
app.logger.info('LDAP Login failed for user "' + form['username'] + ', IP-address :' + ipAdress)
|
|
||||||
flash(_(u"Wrong Username or Password"), category="error")'''
|
|
||||||
else:
|
else:
|
||||||
if user and check_password_hash(user.password, form['password']) and user.nickname is not "Guest":
|
if user and check_password_hash(user.password, form['password']) and user.nickname != "Guest":
|
||||||
login_user(user, remember=True)
|
login_user(user, remember=True)
|
||||||
flash(_(u"You are now logged in as: '%(nickname)s'", nickname=user.nickname), category="success")
|
flash(_(u"You are now logged in as: '%(nickname)s'", nickname=user.nickname), category="success")
|
||||||
return redirect_back(url_for("web.index"))
|
return redirect_back(url_for("web.index"))
|
||||||
else:
|
|
||||||
ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr)
|
ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr)
|
||||||
log.info('Login failed for user "%s" IP-adress: %s', form['username'], ipAdress)
|
log.info('Login failed for user "%s" IP-adress: %s', form['username'], ipAdress)
|
||||||
flash(_(u"Wrong Username or Password"), category="error")
|
flash(_(u"Wrong Username or Password"), category="error")
|
||||||
|
@ -340,6 +340,8 @@ class WorkerThread(threading.Thread):
|
|||||||
check = p.returncode
|
check = p.returncode
|
||||||
calibre_traceback = p.stderr.readlines()
|
calibre_traceback = p.stderr.readlines()
|
||||||
for ele in calibre_traceback:
|
for ele in calibre_traceback:
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
ele = ele.decode('utf-8')
|
||||||
log.debug(ele.strip('\n'))
|
log.debug(ele.strip('\n'))
|
||||||
if not ele.startswith('Traceback') and not ele.startswith(' File'):
|
if not ele.startswith('Traceback') and not ele.startswith(' File'):
|
||||||
error_message = "Calibre failed with error: %s" % ele.strip('\n')
|
error_message = "Calibre failed with error: %s" % ele.strip('\n')
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user