diff --git a/cps.py b/cps.py
index 55d9339c..91bb1d74 100755
--- a/cps.py
+++ b/cps.py
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2012-2019 OzzieIsaacs
+# Copyright (C) 2022 OzzieIsaacs
#
# 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
@@ -17,72 +17,19 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-import sys
import os
+import sys
+# Are we running from commandline?
+if __package__ == '':
+ # Add local path to sys.path so we can import cps
+ path = os.path.dirname(os.path.dirname(__file__))
+ sys.path.insert(0, path)
-# Insert local directories into path
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'vendor'))
-
-
-from cps import create_app
-from cps import web_server
-from cps.opds import opds
-from cps.web import web
-from cps.jinjia import jinjia
-from cps.about import about
-from cps.shelf import shelf
-from cps.admin import admi
-from cps.gdrive import gdrive
-from cps.editbooks import EditBook
-from cps.remotelogin import remotelogin
-from cps.search_metadata import meta
-from cps.error_handler import init_errorhandler
-from cps.schedule import register_scheduled_tasks, register_startup_tasks
-
-try:
- from cps.kobo import kobo, get_kobo_activated
- from cps.kobo_auth import kobo_auth
- kobo_available = get_kobo_activated()
-except (ImportError, AttributeError): # Catch also error for not installed flask-WTF (missing csrf decorator)
- kobo_available = False
-
-try:
- from cps.oauth_bb import oauth
- oauth_available = True
-except ImportError:
- oauth_available = False
-
-
-def main():
- app = create_app()
-
- init_errorhandler()
-
- app.register_blueprint(web)
- app.register_blueprint(opds)
- app.register_blueprint(jinjia)
- app.register_blueprint(about)
- app.register_blueprint(shelf)
- app.register_blueprint(admi)
- app.register_blueprint(remotelogin)
- app.register_blueprint(meta)
- app.register_blueprint(gdrive)
- app.register_blueprint(EditBook)
- if kobo_available:
- app.register_blueprint(kobo)
- app.register_blueprint(kobo_auth)
- if oauth_available:
- app.register_blueprint(oauth)
-
- # Register scheduled tasks
- register_scheduled_tasks() # ToDo only reconnect if reconnect is enabled
- register_startup_tasks()
-
- success = web_server.start()
- sys.exit(0 if success else 1)
-
+from cps.main import main as _main
if __name__ == '__main__':
- main()
+ _main()
+
+
+
diff --git a/cps/__init__.py b/cps/__init__.py
index 68947905..269599ad 100644
--- a/cps/__init__.py
+++ b/cps/__init__.py
@@ -19,8 +19,6 @@
#
# 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
@@ -28,15 +26,23 @@ import mimetypes
from babel import Locale as LC
from babel import negotiate_locale
from babel.core import UnknownLocaleError
-from flask import Flask, request, g
+from flask import request, g
+from flask import Flask
from .MyLoginManager import MyLoginManager
from flask_babel import Babel
from flask_principal import Principal
-from . import config_sql, logger, cache_buster, cli, ub, db
+from . import config_sql
+from . import logger
+from . import cache_buster
+from .cli import CliParameter
+from .constants import CONFIG_DIR
+from . import ub, db
from .reverseproxy import ReverseProxied
from .server import WebServer
from .dep_check import dependency_check
+from . import services
+from .updater import Updater
try:
import lxml
@@ -50,6 +56,7 @@ try:
except ImportError:
wtf_present = False
+
mimetypes.init()
mimetypes.add_type('application/xhtml+xml', '.xhtml')
mimetypes.add_type('application/epub+zip', '.epub')
@@ -81,37 +88,55 @@ app.config.update(
lm = MyLoginManager()
-lm.login_view = 'web.login'
-lm.anonymous_user = ub.Anonymous
-lm.session_protection = 'strong'
-
-if wtf_present:
- csrf = CSRFProtect()
- csrf.init_app(app)
-else:
- csrf = None
-
-ub.init_db(cli.settings_path)
-# pylint: disable=no-member
-config = config_sql.load_configuration(ub.session)
-
-web_server = WebServer()
babel = Babel()
_BABEL_TRANSLATIONS = set()
log = logger.create()
+config = config_sql._ConfigSQL()
-from . import services
-
-db.CalibreDB.update_config(config)
-db.CalibreDB.setup_db(config.config_calibre_dir, cli.settings_path)
+cli_param = CliParameter()
+if wtf_present:
+ csrf = CSRFProtect()
+else:
+ csrf = None
calibre_db = db.CalibreDB()
+web_server = WebServer()
+
+updater_thread = Updater()
+
+
def create_app():
+ lm.login_view = 'web.login'
+ lm.anonymous_user = ub.Anonymous
+ lm.session_protection = 'strong'
+
+ if csrf:
+ csrf.init_app(app)
+
+ cli_param.init()
+
+ ub.init_db(os.path.join(CONFIG_DIR, "app.db"), cli_param.user_credentials)
+
+ # ub.init_db(os.path.join(CONFIG_DIR, "app.db"))
+ # pylint: disable=no-member
+ config_sql.load_configuration(config, ub.session, cli_param)
+
+ 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 afterwards
+ if cli_param.dry_run:
+ updater_thread.dry_run()
+ sys.exit(0)
+ updater_thread.start()
+
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 ***')
@@ -156,7 +181,7 @@ def create_app():
services.goodreads_support.connect(config.config_goodreads_api_key,
config.config_goodreads_api_secret,
config.config_use_goodreads)
- config.store_calibre_uuid(calibre_db, db.LibraryId)
+ config.store_calibre_uuid(calibre_db, db.Library_Id)
return app
@@ -179,11 +204,8 @@ def get_locale():
return negotiate_locale(preferred or ['en'], _BABEL_TRANSLATIONS)
-from .updater import Updater
-updater_thread = Updater()
+'''@babel.timezoneselector
+def get_timezone():
+ user = getattr(g, 'user', None)
+ return user.timezone if user else None'''
-# Perform dry run of updater and exit afterwards
-if cli.dry_run:
- updater_thread.dry_run()
- sys.exit(0)
-updater_thread.start()
diff --git a/cps/admin.py b/cps/admin.py
index 73442ef2..46da062e 100644
--- a/cps/admin.py
+++ b/cps/admin.py
@@ -1254,7 +1254,7 @@ def _db_configuration_update_helper():
if not calibre_db.setup_db(to_save['config_calibre_dir'], ub.app_DB_path):
return _db_configuration_result(_('DB Location is not Valid, Please Enter Correct Path'),
gdrive_error)
- config.store_calibre_uuid(calibre_db, db.LibraryId)
+ config.store_calibre_uuid(calibre_db, db.Library_Id)
# if db changed -> delete shelfs, delete download books, delete read books, kobo sync...
if db_change:
log.info("Calibre Database changed, all Calibre-Web info related to old Database gets deleted")
diff --git a/cps/cli.py b/cps/cli.py
index 629fd76d..cf4f36fb 100644
--- a/cps/cli.py
+++ b/cps/cli.py
@@ -31,96 +31,99 @@ def version_info():
return "Calibre-Web version: %s - unkown git-clone" % _STABLE_VERSION['version']
return "Calibre-Web version: %s -%s" % (_STABLE_VERSION['version'], _NIGHTLY_VERSION[1])
+class CliParameter(object):
-parser = argparse.ArgumentParser(description='Calibre Web is a web app'
- ' providing a interface for browsing, reading and downloading eBooks\n', prog='cps.py')
-parser.add_argument('-p', metavar='path', help='path and name to settings db, e.g. /opt/cw.db')
-parser.add_argument('-g', metavar='path', help='path and name to gdrive db, e.g. /opt/gd.db')
-parser.add_argument('-c', metavar='path',
- help='path and name to SSL certfile, e.g. /opt/test.cert, works only in combination with keyfile')
-parser.add_argument('-k', metavar='path',
- help='path and name to SSL keyfile, e.g. /opt/test.key, works only in combination with certfile')
-parser.add_argument('-v', '--version', action='version', help='Shows version number and exits Calibre-Web',
- version=version_info())
-parser.add_argument('-i', metavar='ip-address', help='Server IP-Address to listen')
-parser.add_argument('-s', metavar='user:pass', help='Sets specific username to new password and exits Calibre-Web')
-parser.add_argument('-f', action='store_true', help='Flag is depreciated and will be removed in next version')
-parser.add_argument('-l', action='store_true', help='Allow loading covers from localhost')
-parser.add_argument('-d', action='store_true', help='Dry run of updater to check file permissions in advance '
- 'and exits Calibre-Web')
-parser.add_argument('-r', action='store_true', help='Enable public database reconnect route under /reconnect')
-args = parser.parse_args()
+ def init(self):
+ self.arg_parser()
-settings_path = args.p or os.path.join(_CONFIG_DIR, DEFAULT_SETTINGS_FILE)
-gd_path = args.g or os.path.join(_CONFIG_DIR, DEFAULT_GDRIVE_FILE)
+ def arg_parser(self):
+ parser = argparse.ArgumentParser(description='Calibre Web is a web app'
+ ' providing a interface for browsing, reading and downloading eBooks\n',
+ prog='cps.py')
+ parser.add_argument('-p', metavar='path', help='path and name to settings db, e.g. /opt/cw.db')
+ parser.add_argument('-g', metavar='path', help='path and name to gdrive db, e.g. /opt/gd.db')
+ parser.add_argument('-c', metavar='path',
+ help='path and name to SSL certfile, e.g. /opt/test.cert, works only in combination with keyfile')
+ parser.add_argument('-k', metavar='path',
+ help='path and name to SSL keyfile, e.g. /opt/test.key, works only in combination with certfile')
+ parser.add_argument('-v', '--version', action='version', help='Shows version number and exits Calibre-Web',
+ version=version_info())
+ parser.add_argument('-i', metavar='ip-address', help='Server IP-Address to listen')
+ parser.add_argument('-s', metavar='user:pass',
+ help='Sets specific username to new password and exits Calibre-Web')
+ parser.add_argument('-f', action='store_true', help='Flag is depreciated and will be removed in next version')
+ parser.add_argument('-l', action='store_true', help='Allow loading covers from localhost')
+ parser.add_argument('-d', action='store_true', help='Dry run of updater to check file permissions in advance '
+ 'and exits Calibre-Web')
+ parser.add_argument('-r', action='store_true', help='Enable public database reconnect route under /reconnect')
+ args = parser.parse_args()
-if os.path.isdir(settings_path):
- settings_path = os.path.join(settings_path, DEFAULT_SETTINGS_FILE)
+ self.settings_path = args.p or os.path.join(_CONFIG_DIR, DEFAULT_SETTINGS_FILE)
+ self.gd_path = args.g or os.path.join(_CONFIG_DIR, DEFAULT_GDRIVE_FILE)
-if os.path.isdir(gd_path):
- gd_path = os.path.join(gd_path, DEFAULT_GDRIVE_FILE)
+ if os.path.isdir(self.settings_path):
+ self.settings_path = os.path.join(self.settings_path, DEFAULT_SETTINGS_FILE)
+
+ if os.path.isdir(self.gd_path):
+ self.gd_path = os.path.join(self.gd_path, DEFAULT_GDRIVE_FILE)
-# handle and check parameter for ssl encryption
-certfilepath = None
-keyfilepath = None
-if args.c:
- if os.path.isfile(args.c):
- certfilepath = args.c
- else:
- print("Certfile path is invalid. Exiting...")
- sys.exit(1)
-
-if args.c == "":
- certfilepath = ""
-
-if args.k:
- if os.path.isfile(args.k):
- keyfilepath = args.k
- else:
- print("Keyfile path is invalid. Exiting...")
- sys.exit(1)
-
-if (args.k and not args.c) or (not args.k and args.c):
- print("Certfile and Keyfile have to be used together. Exiting...")
- sys.exit(1)
-
-if args.k == "":
- keyfilepath = ""
-
-
-# dry run updater
-dry_run = args.d or None
-# enable reconnect endpoint for docker database reconnect
-reconnect_enable = args.r or os.environ.get("CALIBRE_RECONNECT", None)
-# load covers from localhost
-allow_localhost = args.l or os.environ.get("CALIBRE_LOCALHOST", None)
-# handle and check ip address argument
-ip_address = args.i or None
-
-
-if ip_address:
- try:
- # try to parse the given ip address with socket
- if hasattr(socket, 'inet_pton'):
- if ':' in ip_address:
- socket.inet_pton(socket.AF_INET6, ip_address)
+ # handle and check parameter for ssl encryption
+ self.certfilepath = None
+ self.keyfilepath = None
+ if args.c:
+ if os.path.isfile(args.c):
+ self.certfilepath = args.c
else:
- socket.inet_pton(socket.AF_INET, ip_address)
- else:
- # on windows python < 3.4, inet_pton is not available
- # inet_atom only handles IPv4 addresses
- socket.inet_aton(ip_address)
- except socket.error as err:
- print(ip_address, ':', err)
- sys.exit(1)
+ print("Certfile path is invalid. Exiting...")
+ sys.exit(1)
-# handle and check user password argument
-user_credentials = args.s or None
-if user_credentials and ":" not in user_credentials:
- print("No valid 'username:password' format")
- sys.exit(3)
+ if args.c == "":
+ self.certfilepath = ""
-if args.f:
- print("Warning: -f flag is depreciated and will be removed in next version")
+ if args.k:
+ if os.path.isfile(args.k):
+ self.keyfilepath = args.k
+ else:
+ print("Keyfile path is invalid. Exiting...")
+ sys.exit(1)
+ if (args.k and not args.c) or (not args.k and args.c):
+ print("Certfile and Keyfile have to be used together. Exiting...")
+ sys.exit(1)
+
+ if args.k == "":
+ self.keyfilepath = ""
+
+ # dry run updater
+ self.dry_run =args.d or None
+ # enable reconnect endpoint for docker database reconnect
+ self.reconnect_enable = args.r or os.environ.get("CALIBRE_RECONNECT", None)
+ # load covers from localhost
+ self.allow_localhost = args.l or os.environ.get("CALIBRE_LOCALHOST", None)
+ # handle and check ip address argument
+ self.ip_address = args.i or None
+ if self.ip_address:
+ try:
+ # try to parse the given ip address with socket
+ if hasattr(socket, 'inet_pton'):
+ if ':' in self.ip_address:
+ socket.inet_pton(socket.AF_INET6, self.ip_address)
+ else:
+ socket.inet_pton(socket.AF_INET, self.ip_address)
+ else:
+ # on windows python < 3.4, inet_pton is not available
+ # inet_atom only handles IPv4 addresses
+ socket.inet_aton(self.ip_address)
+ except socket.error as err:
+ print(self.ip_address, ':', err)
+ sys.exit(1)
+
+ # handle and check user password argument
+ self.user_credentials = args.s or None
+ if self.user_credentials and ":" not in self.user_credentials:
+ print("No valid 'username:password' format")
+ sys.exit(3)
+
+ if args.f:
+ print("Warning: -f flag is depreciated and will be removed in next version")
diff --git a/cps/config_sql.py b/cps/config_sql.py
index f4fbb554..e20f7ac2 100644
--- a/cps/config_sql.py
+++ b/cps/config_sql.py
@@ -29,7 +29,7 @@ try:
except ImportError:
from sqlalchemy.ext.declarative import declarative_base
-from . import constants, cli, logger
+from . import constants, logger
log = logger.create()
@@ -154,12 +154,16 @@ class _Settings(_Base):
# Class holds all application specific settings in calibre-web
class _ConfigSQL(object):
# pylint: disable=no-member
- def __init__(self, session):
+ def __init__(self):
+ pass
+
+ def init_config(self, session, cli):
self._session = session
self._settings = None
self.db_configured = None
self.config_calibre_dir = None
self.load()
+ self.cli = cli
change = False
if self.config_converterpath == None: # pylint: disable=access-member-before-definition
@@ -184,22 +188,21 @@ class _ConfigSQL(object):
return self._settings
def get_config_certfile(self):
- if cli.certfilepath:
- return cli.certfilepath
- if cli.certfilepath == "":
+ if self.cli.certfilepath:
+ return self.cli.certfilepath
+ if self.cli.certfilepath == "":
return None
return self.config_certfile
def get_config_keyfile(self):
- if cli.keyfilepath:
- return cli.keyfilepath
- if cli.certfilepath == "":
+ if self.cli.keyfilepath:
+ return self.cli.keyfilepath
+ if self.cli.certfilepath == "":
return None
return self.config_keyfile
- @staticmethod
- def get_config_ipaddress():
- return cli.ip_address or ""
+ def get_config_ipaddress(self):
+ return self.cli.ip_address or ""
def _has_role(self, role_flag):
return constants.has_flag(self.config_default_role, role_flag)
@@ -449,14 +452,15 @@ def _migrate_database(session):
_migrate_table(session, _Flask_Settings)
-def load_configuration(session):
+def load_configuration(conf, session, cli):
_migrate_database(session)
if not session.query(_Settings).count():
session.add(_Settings())
session.commit()
- conf = _ConfigSQL(session)
- return conf
+ # conf = _ConfigSQL()
+ conf.init_config(session, cli)
+ # return conf
def get_flask_session_key(_session):
flask_settings = _session.query(_Flask_Settings).one_or_none()
diff --git a/cps/db.py b/cps/db.py
index 9f6440aa..69796c51 100644
--- a/cps/db.py
+++ b/cps/db.py
@@ -89,7 +89,7 @@ books_publishers_link = Table('books_publishers_link', Base.metadata,
)
-class LibraryId(Base):
+class Library_Id(Base):
__tablename__ = 'library_id'
id = Column(Integer, primary_key=True)
uuid = Column(String, nullable=False)
@@ -440,10 +440,12 @@ class CalibreDB:
# instances alive once they reach the end of their respective scopes
instances = WeakSet()
- def __init__(self, expire_on_commit=True):
+ def __init__(self):
""" Initialize a new CalibreDB session
"""
self.session = None
+
+ def init_db(self, expire_on_commit=True):
if self._init:
self.init_session(expire_on_commit)
@@ -543,7 +545,7 @@ class CalibreDB:
connection.execute(text("attach database '{}' as app_settings;".format(app_db_path)))
local_session = scoped_session(sessionmaker())
local_session.configure(bind=connection)
- database_uuid = local_session().query(LibraryId).one_or_none()
+ database_uuid = local_session().query(Library_Id).one_or_none()
# local_session.dispose()
check_engine.connect()
diff --git a/cps/editbooks.py b/cps/editbooks.py
index 768f1830..9ac1557b 100755
--- a/cps/editbooks.py
+++ b/cps/editbooks.py
@@ -49,7 +49,7 @@ from .usermanagement import login_required_if_no_ano
from .kobo_sync_status import change_archived_books
-EditBook = Blueprint('edit-book', __name__)
+editbook = Blueprint('edit-book', __name__)
log = logger.create()
@@ -228,14 +228,14 @@ def modify_identifiers(input_identifiers, db_identifiers, db_session):
return changed, error
-@EditBook.route("/ajax/delete/", methods=["POST"])
+@editbook.route("/ajax/delete/", methods=["POST"])
@login_required
def delete_book_from_details(book_id):
return Response(delete_book_from_table(book_id, "", True), mimetype='application/json')
-@EditBook.route("/delete/", defaults={'book_format': ""}, methods=["POST"])
-@EditBook.route("/delete//", methods=["POST"])
+@editbook.route("/delete/", defaults={'book_format': ""}, methods=["POST"])
+@editbook.route("/delete//", methods=["POST"])
@login_required
def delete_book_ajax(book_id, book_format):
return delete_book_from_table(book_id, book_format, False)
@@ -743,14 +743,14 @@ def handle_author_on_edit(book, author_name, update_stored=True):
return input_authors, change, renamed
-@EditBook.route("/admin/book/", methods=['GET'])
+@editbook.route("/admin/book/", methods=['GET'])
@login_required_if_no_ano
@edit_required
def show_edit_book(book_id):
return render_edit_book(book_id)
-@EditBook.route("/admin/book/", methods=['POST'])
+@editbook.route("/admin/book/", methods=['POST'])
@login_required_if_no_ano
@edit_required
def edit_book(book_id):
@@ -1084,7 +1084,7 @@ def move_coverfile(meta, db_book):
category="error")
-@EditBook.route("/upload", methods=["POST"])
+@editbook.route("/upload", methods=["POST"])
@login_required_if_no_ano
@upload_required
def upload():
@@ -1153,7 +1153,7 @@ def upload():
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
-@EditBook.route("/admin/book/convert/", methods=['POST'])
+@editbook.route("/admin/book/convert/", methods=['POST'])
@login_required_if_no_ano
@edit_required
def convert_bookformat(book_id):
@@ -1178,7 +1178,7 @@ def convert_bookformat(book_id):
return redirect(url_for('edit-book.show_edit_book', book_id=book_id))
-@EditBook.route("/ajax/getcustomenum/")
+@editbook.route("/ajax/getcustomenum/")
@login_required
def table_get_custom_enum(c_id):
ret = list()
@@ -1191,7 +1191,7 @@ def table_get_custom_enum(c_id):
return json.dumps(ret)
-@EditBook.route("/ajax/editbooks/", methods=['POST'])
+@editbook.route("/ajax/editbooks/", methods=['POST'])
@login_required_if_no_ano
@edit_required
def edit_list_book(param):
@@ -1303,7 +1303,7 @@ def edit_list_book(param):
return ret
-@EditBook.route("/ajax/sort_value//")
+@editbook.route("/ajax/sort_value//")
@login_required
def get_sorted_entry(field, bookid):
if field in ['title', 'authors', 'sort', 'author_sort']:
@@ -1320,7 +1320,7 @@ def get_sorted_entry(field, bookid):
return ""
-@EditBook.route("/ajax/simulatemerge", methods=['POST'])
+@editbook.route("/ajax/simulatemerge", methods=['POST'])
@login_required
@edit_required
def simulate_merge_list_book():
@@ -1336,7 +1336,7 @@ def simulate_merge_list_book():
return ""
-@EditBook.route("/ajax/mergebooks", methods=['POST'])
+@editbook.route("/ajax/mergebooks", methods=['POST'])
@login_required
@edit_required
def merge_list_book():
@@ -1374,7 +1374,7 @@ def merge_list_book():
return ""
-@EditBook.route("/ajax/xchange", methods=['POST'])
+@editbook.route("/ajax/xchange", methods=['POST'])
@login_required
@edit_required
def table_xchange_author_title():
diff --git a/cps/gdriveutils.py b/cps/gdriveutils.py
index ee8ee953..9990e3db 100644
--- a/cps/gdriveutils.py
+++ b/cps/gdriveutils.py
@@ -63,7 +63,7 @@ except ImportError as err:
importError = err
gdrive_support = False
-from . import logger, cli, config
+from . import logger, cli_param, config
from .constants import CONFIG_DIR as _CONFIG_DIR
@@ -142,7 +142,7 @@ def is_gdrive_ready():
return os.path.exists(SETTINGS_YAML) and os.path.exists(CREDENTIALS)
-engine = create_engine('sqlite:///{0}'.format(cli.gd_path), echo=False)
+engine = create_engine('sqlite:///{0}'.format(cli_param.gd_path), echo=False)
Base = declarative_base()
# Open session for database connection
@@ -190,11 +190,11 @@ def migrate():
session.execute('ALTER TABLE gdrive_ids2 RENAME to gdrive_ids')
break
-if not os.path.exists(cli.gd_path):
+if not os.path.exists(cli_param.gd_path):
try:
Base.metadata.create_all(engine)
except Exception as ex:
- log.error("Error connect to database: {} - {}".format(cli.gd_path, ex))
+ log.error("Error connect to database: {} - {}".format(cli_param.gd_path, ex))
raise
migrate()
@@ -544,6 +544,7 @@ def deleteDatabaseOnChange():
except (OperationalError, InvalidRequestError) as ex:
session.rollback()
log.error_or_exception('Database error: {}'.format(ex))
+ session.rollback()
def updateGdriveCalibreFromLocal():
diff --git a/cps/main.py b/cps/main.py
new file mode 100644
index 00000000..b960028e
--- /dev/null
+++ b/cps/main.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+
+# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
+# Copyright (C) 2012-2022 OzzieIsaacs
+#
+# 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 .
+
+import sys
+
+from . import create_app
+from .jinjia import jinjia
+from .shelf import shelf
+from .remotelogin import remotelogin
+from .search_metadata import meta
+from .error_handler import init_errorhandler
+
+try:
+ from kobo import kobo, get_kobo_activated
+ from kobo_auth import kobo_auth
+ kobo_available = get_kobo_activated()
+except (ImportError, AttributeError): # Catch also error for not installed flask-WTF (missing csrf decorator)
+ kobo_available = False
+
+try:
+ from oauth_bb import oauth
+ oauth_available = True
+except ImportError:
+ oauth_available = False
+
+
+def main():
+ app = create_app()
+
+ from .web import web
+ from .opds import opds
+ from .admin import admi
+ from .gdrive import gdrive
+ from .editbooks import editbook
+ from .about import about
+
+ from . import web_server
+ init_errorhandler()
+
+ app.register_blueprint(web)
+ app.register_blueprint(opds)
+ app.register_blueprint(jinjia)
+ app.register_blueprint(about)
+ app.register_blueprint(shelf)
+ app.register_blueprint(admi) #
+ app.register_blueprint(remotelogin)
+ app.register_blueprint(meta)
+ app.register_blueprint(gdrive)
+ app.register_blueprint(editbook)
+ if kobo_available:
+ app.register_blueprint(kobo)
+ app.register_blueprint(kobo_auth)
+ if oauth_available:
+ app.register_blueprint(oauth)
+ success = web_server.start()
+ sys.exit(0 if success else 1)
diff --git a/cps/tasks/mail.py b/cps/tasks/mail.py
index ad38a400..be240c79 100755
--- a/cps/tasks/mail.py
+++ b/cps/tasks/mail.py
@@ -24,11 +24,10 @@ import mimetypes
from io import StringIO
from email.message import EmailMessage
-from email.utils import parseaddr
-
+from email.utils import formatdate, parseaddr
+from email.generator import Generator
from flask_babel import lazy_gettext as N_
from email.utils import formatdate
-from email.generator import Generator
from cps.services.worker import CalibreTask
from cps.services import gmail
diff --git a/cps/templates/admin.html b/cps/templates/admin.html
index 8a20b73e..f1c3749a 100644
--- a/cps/templates/admin.html
+++ b/cps/templates/admin.html
@@ -210,7 +210,7 @@
-
{{_('Update')}}
+
{{_('Version Information')}}
diff --git a/cps/ub.py b/cps/ub.py
index 0360b572..fdeff2bc 100644
--- a/cps/ub.py
+++ b/cps/ub.py
@@ -53,7 +53,7 @@ except ImportError:
from sqlalchemy.orm import backref, relationship, sessionmaker, Session, scoped_session
from werkzeug.security import generate_password_hash
-from . import constants, logger, cli
+from . import constants, logger
log = logger.create()
@@ -816,7 +816,7 @@ def init_db_thread():
return Session()
-def init_db(app_db_path):
+def init_db(app_db_path, user_credentials=None):
# Open session for database connection
global session
global app_DB_path
@@ -837,8 +837,8 @@ def init_db(app_db_path):
create_admin_user(session)
create_anonymous_user(session)
- if cli.user_credentials:
- username, password = cli.user_credentials.split(':', 1)
+ if user_credentials:
+ username, password = user_credentials.split(':', 1)
user = session.query(User).filter(func.lower(User.name) == username.lower()).first()
if user:
if not password:
diff --git a/cps/updater.py b/cps/updater.py
index 021df005..f651719e 100644
--- a/cps/updater.py
+++ b/cps/updater.py
@@ -31,7 +31,7 @@ import requests
from babel.dates import format_datetime
from flask_babel import gettext as _
-from . import constants, logger, config, web_server
+from . import constants, logger # config, web_server
log = logger.create()
@@ -58,13 +58,17 @@ class Updater(threading.Thread):
self.status = -1
self.updateIndex = None
+ def init_updater(self, config, web_server):
+ self.config = config
+ self.web_server = web_server
+
def get_current_version_info(self):
- if config.config_updatechannel == constants.UPDATE_STABLE:
+ if self.config.config_updatechannel == constants.UPDATE_STABLE:
return self._stable_version_info()
return self._nightly_version_info()
def get_available_updates(self, request_method, locale):
- if config.config_updatechannel == constants.UPDATE_STABLE:
+ if self.config.config_updatechannel == constants.UPDATE_STABLE:
return self._stable_available_updates(request_method)
return self._nightly_available_updates(request_method, locale)
@@ -95,7 +99,7 @@ class Updater(threading.Thread):
self.status = 6
log.debug(u'Preparing restart of server')
time.sleep(2)
- web_server.stop(True)
+ self.web_server.stop(True)
self.status = 7
time.sleep(2)
return True
diff --git a/setup.cfg b/setup.cfg
index 5e427849..9daae0c9 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -54,7 +54,7 @@ install_requires =
tornado>=4.1,<6.2
Wand>=0.4.4,<0.7.0
unidecode>=0.04.19,<1.4.0
- lxml>=3.8.0,<4.8.0
+ lxml>=3.8.0,<4.9.0
flask-wtf>=0.14.2,<1.1.0
chardet>=3.0.0,<4.1.0
advocate>=1.0.0,<1.1.0
@@ -62,7 +62,7 @@ install_requires =
[options.extras_require]
gdrive =
- google-api-python-client>=1.7.11,<2.44.0
+ google-api-python-client>=1.7.11,<2.46.0
gevent>20.6.0,<22.0.0
greenlet>=0.4.17,<1.2.0
httplib2>=0.9.2,<0.21.0
@@ -75,7 +75,7 @@ gdrive =
rsa>=3.4.2,<4.9.0
gmail =
google-auth-oauthlib>=0.4.3,<0.6.0
- google-api-python-client>=1.7.11,<2.44.0
+ google-api-python-client>=1.7.11,<2.46.0
goodreads =
goodreads>=0.3.2,<0.4.0
python-Levenshtein>=0.12.0,<0.13.0