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