# -*- coding: utf-8 -*- # This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web) # Copyright (C) 2018-2019 OzzieIsaacs, cervinko, jkrehm, bodybybuddha, ok11, # andy29485, idalin, Kyosfonica, wuqi, Kennyl, lemmsh, # falgh1, grunjol, csitko, ytils, xybydy, trasba, vrabe, # ruben-herold, marblepebble, JackED42, SiphonSquirrel, # apetresc, nanu-c, mutschler # # 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 . __package__ = "cps" import sys import os import mimetypes from flask import Flask from .MyLoginManager import MyLoginManager from flask_principal import Principal from . import logger from .cli import CliParameter from .constants import CONFIG_DIR from .reverseproxy import ReverseProxied from .server import WebServer from .dep_check import dependency_check from .updater import Updater from .babel import babel, get_locale from . import config_sql from . import cache_buster from . import ub, db try: from flask_limiter import Limiter limiter_present = True except ImportError: limiter_present = False try: from flask_wtf.csrf import CSRFProtect wtf_present = True except ImportError: wtf_present = False mimetypes.init() mimetypes.add_type('application/xhtml+xml', '.xhtml') mimetypes.add_type('application/epub+zip', '.epub') mimetypes.add_type('application/fb2+zip', '.fb2') mimetypes.add_type('application/x-mobipocket-ebook', '.mobi') mimetypes.add_type('application/x-mobipocket-ebook', '.prc') mimetypes.add_type('application/vnd.amazon.ebook', '.azw') mimetypes.add_type('application/x-mobi8-ebook', '.azw3') mimetypes.add_type('application/x-cbr', '.cbr') mimetypes.add_type('application/x-cbz', '.cbz') mimetypes.add_type('application/x-cbt', '.cbt') mimetypes.add_type('application/x-cb7', '.cb7') mimetypes.add_type('image/vnd.djv', '.djv') mimetypes.add_type('application/mpeg', '.mpeg') mimetypes.add_type('application/mpeg', '.mp3') mimetypes.add_type('application/mp4', '.m4a') mimetypes.add_type('application/mp4', '.m4b') mimetypes.add_type('application/ogg', '.ogg') mimetypes.add_type('application/ogg', '.oga') mimetypes.add_type('text/css', '.css') mimetypes.add_type('text/javascript; charset=UTF-8', '.js') log = logger.create() app = Flask(__name__) app.config.update( SESSION_COOKIE_HTTPONLY=True, SESSION_COOKIE_SAMESITE='Lax', REMEMBER_COOKIE_SAMESITE='Lax', # will be available in flask-login 0.5.1 earliest WTF_CSRF_SSL_STRICT=False ) lm = MyLoginManager() cli_param = CliParameter() config = config_sql.ConfigSQL() if wtf_present: csrf = CSRFProtect() else: csrf = None calibre_db = db.CalibreDB() web_server = WebServer() updater_thread = Updater() if limiter_present: limiter = Limiter(key_func=True, headers_enabled=True, auto_check=False, swallow_errors=False) else: limiter = None def create_app(): if csrf: csrf.init_app(app) cli_param.init() ub.init_db(cli_param.settings_path) # pylint: disable=no-member encrypt_key, error = config_sql.get_encryption_key(os.path.dirname(cli_param.settings_path)) config_sql.load_configuration(ub.session, encrypt_key) config.init_config(ub.session, encrypt_key, cli_param) if error: log.error(error) ub.password_change(cli_param.user_credentials) if sys.version_info < (3, 0): log.info( '*** Python2 is EOL since end of 2019, this version of Calibre-Web is no longer supporting Python2, ' 'please update your installation to Python3 ***') print( '*** Python2 is EOL since end of 2019, this version of Calibre-Web is no longer supporting Python2, ' 'please update your installation to Python3 ***') web_server.stop(True) sys.exit(5) lm.login_view = 'web.login' lm.anonymous_user = ub.Anonymous lm.session_protection = 'strong' if config.config_session == 1 else "basic" db.CalibreDB.update_config(config) db.CalibreDB.setup_db(config.config_calibre_dir, cli_param.settings_path) calibre_db.init_db() updater_thread.init_updater(config, web_server) # Perform dry run of updater and exit afterward if cli_param.dry_run: updater_thread.dry_run() sys.exit(0) updater_thread.start() requirements = dependency_check() for res in requirements: if res['found'] == "not installed": message = ('Cannot import {name} module, it is needed to run calibre-web, ' 'please install it using "pip install {name}"').format(name=res["name"]) log.info(message) print("*** " + message + " ***") web_server.stop(True) sys.exit(8) for res in requirements + dependency_check(True): log.info('*** "{}" version does not meet the requirements. ' 'Should: {}, Found: {}, please consider installing required version ***' .format(res['name'], res['target'], res['found'])) app.wsgi_app = ReverseProxied(app.wsgi_app) if os.environ.get('FLASK_DEBUG'): cache_buster.init_cache_busting(app) log.info('Starting Calibre Web...') Principal(app) lm.init_app(app) app.secret_key = os.getenv('SECRET_KEY', config_sql.get_flask_session_key(ub.session)) web_server.init_app(app, config) if hasattr(babel, "localeselector"): babel.init_app(app) babel.localeselector(get_locale) else: babel.init_app(app, locale_selector=get_locale) from . import services if services.ldap: services.ldap.init_app(app, config) if services.goodreads_support: services.goodreads_support.connect(config.config_goodreads_api_key, config.config_use_goodreads) config.store_calibre_uuid(calibre_db, db.Library_Id) # Configure rate limiter # https://limits.readthedocs.io/en/stable/storage.html app.config.update(RATELIMIT_ENABLED=config.config_ratelimiter) if config.config_limiter_uri != "" and not cli_param.memory_backend: app.config.update(RATELIMIT_STORAGE_URI=config.config_limiter_uri) if config.config_limiter_options != "": app.config.update(RATELIMIT_STORAGE_OPTIONS=config.config_limiter_options) try: limiter.init_app(app) except Exception as e: log.error('Wrong Flask Limiter configuration, falling back to default: {}'.format(e)) app.config.update(RATELIMIT_STORAGE_URI=None) limiter.init_app(app) # Register scheduled tasks from .schedule import register_scheduled_tasks, register_startup_tasks register_scheduled_tasks(config.schedule_reconnect) register_startup_tasks() return app