Refactored startup for compatibility with pyinstaller 5.0

This commit is contained in:
Ozzie Isaacs
2022-04-26 11:10:44 +02:00
parent db03fb3edd
commit 9410b47144
14 changed files with 283 additions and 230 deletions
+53 -31
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()
+1 -1
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")
+86 -83
View File
@@ -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")
+18 -14
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()
+5 -3
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()
+14 -14
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():
+5 -4
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
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)
+2 -3
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
+1 -1
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>
+4 -4
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:
+8 -4
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