Refactored startup for compatibility with pyinstaller 5.0

This commit is contained in:
Ozzie Isaacs 2022-04-26 11:04:00 +02:00
parent db03fb3edd
commit 9410b47144
14 changed files with 283 additions and 230 deletions

77
cps.py
View File

@ -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 <http://www.gnu.org/licenses/>.
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()

View File

@ -19,8 +19,6 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
__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()

View File

@ -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")

View File

@ -31,9 +31,15 @@ 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):
def init(self):
self.arg_parser()
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')
' 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',
@ -43,7 +49,8 @@ parser.add_argument('-k', metavar='path',
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('-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 '
@ -51,32 +58,32 @@ parser.add_argument('-d', action='store_true', help='Dry run of updater to check
parser.add_argument('-r', action='store_true', help='Enable public database reconnect route under /reconnect')
args = parser.parse_args()
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)
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(settings_path):
settings_path = os.path.join(settings_path, DEFAULT_SETTINGS_FILE)
if os.path.isdir(self.settings_path):
self.settings_path = os.path.join(self.settings_path, DEFAULT_SETTINGS_FILE)
if os.path.isdir(gd_path):
gd_path = os.path.join(gd_path, DEFAULT_GDRIVE_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
self.certfilepath = None
self.keyfilepath = None
if args.c:
if os.path.isfile(args.c):
certfilepath = args.c
self.certfilepath = args.c
else:
print("Certfile path is invalid. Exiting...")
sys.exit(1)
if args.c == "":
certfilepath = ""
self.certfilepath = ""
if args.k:
if os.path.isfile(args.k):
keyfilepath = args.k
self.keyfilepath = args.k
else:
print("Keyfile path is invalid. Exiting...")
sys.exit(1)
@ -86,41 +93,37 @@ if (args.k and not args.c) or (not args.k and args.c):
sys.exit(1)
if args.k == "":
keyfilepath = ""
self.keyfilepath = ""
# dry run updater
dry_run = args.d or None
self.dry_run =args.d or None
# enable reconnect endpoint for docker database reconnect
reconnect_enable = args.r or os.environ.get("CALIBRE_RECONNECT", None)
self.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)
self.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:
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 ip_address:
socket.inet_pton(socket.AF_INET6, ip_address)
if ':' in self.ip_address:
socket.inet_pton(socket.AF_INET6, self.ip_address)
else:
socket.inet_pton(socket.AF_INET, ip_address)
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(ip_address)
socket.inet_aton(self.ip_address)
except socket.error as err:
print(ip_address, ':', err)
print(self.ip_address, ':', err)
sys.exit(1)
# handle and check user password argument
user_credentials = args.s or None
if user_credentials and ":" not in user_credentials:
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")

View File

@ -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()

View File

@ -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()

View File

@ -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/<int:book_id>", methods=["POST"])
@editbook.route("/ajax/delete/<int:book_id>", 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/<int:book_id>", defaults={'book_format': ""}, methods=["POST"])
@EditBook.route("/delete/<int:book_id>/<string:book_format>", methods=["POST"])
@editbook.route("/delete/<int:book_id>", defaults={'book_format': ""}, methods=["POST"])
@editbook.route("/delete/<int:book_id>/<string:book_format>", 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/<int:book_id>", methods=['GET'])
@editbook.route("/admin/book/<int:book_id>", 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/<int:book_id>", methods=['POST'])
@editbook.route("/admin/book/<int:book_id>", 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/<int:book_id>", methods=['POST'])
@editbook.route("/admin/book/convert/<int:book_id>", 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/<int:c_id>")
@editbook.route("/ajax/getcustomenum/<int:c_id>")
@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/<param>", methods=['POST'])
@editbook.route("/ajax/editbooks/<param>", 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/<field>/<int:bookid>")
@editbook.route("/ajax/sort_value/<field>/<int:bookid>")
@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():

View File

@ -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():

71
cps/main.py Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
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)

View File

@ -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

View File

@ -210,7 +210,7 @@
<div class="row">
<div class="col">
<h2>{{_('Update')}}</h2>
<h2>{{_('Version Information')}}</h2>
<table class="table table-striped" id="update_table">
<thead>
<tr>

View File

@ -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:

View File

@ -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

View File

@ -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