diff --git a/cps/about.py b/cps/about.py index 6ea584f4..2e9cc699 100644 --- a/cps/about.py +++ b/cps/about.py @@ -48,6 +48,11 @@ try: except ImportError: flask_danceVersion = None +try: + from greenlet import __version__ as greenlet_Version +except ImportError: + greenlet_Version = None + from . import services about = flask.Blueprint('about', __name__) @@ -77,7 +82,8 @@ _VERSIONS = OrderedDict( python_LDAP = services.ldapVersion if bool(services.ldapVersion) else None, Goodreads = u'installed' if bool(services.goodreads_support) else None, jsonschema = services.SyncToken.__version__ if bool(services.SyncToken) else None, - flask_dance = flask_danceVersion + flask_dance = flask_danceVersion, + greenlet = greenlet_Version ) _VERSIONS.update(uploader.get_versions()) diff --git a/cps/admin.py b/cps/admin.py index 03b306a8..767aece3 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -56,7 +56,8 @@ log = logger.create() feature_support = { 'ldap': bool(services.ldap), 'goodreads': bool(services.goodreads_support), - 'kobo': bool(services.kobo) + 'kobo': bool(services.kobo), + 'updater': constants.UPDATER_AVAILABLE } try: @@ -1264,8 +1265,11 @@ def download_debug(): @login_required @admin_required def get_update_status(): - log.info(u"Update status requested") - return updater_thread.get_available_updates(request.method, locale=get_locale()) + if feature_support['updater']: + log.info(u"Update status requested") + return updater_thread.get_available_updates(request.method, locale=get_locale()) + else: + return '' @admi.route("/get_updater_status", methods=['GET', 'POST']) @@ -1273,35 +1277,37 @@ def get_update_status(): @admin_required def get_updater_status(): status = {} - if request.method == "POST": - commit = request.form.to_dict() - if "start" in commit and commit['start'] == 'True': - text = { - "1": _(u'Requesting update package'), - "2": _(u'Downloading update package'), - "3": _(u'Unzipping update package'), - "4": _(u'Replacing files'), - "5": _(u'Database connections are closed'), - "6": _(u'Stopping server'), - "7": _(u'Update finished, please press okay and reload page'), - "8": _(u'Update failed:') + u' ' + _(u'HTTP Error'), - "9": _(u'Update failed:') + u' ' + _(u'Connection error'), - "10": _(u'Update failed:') + u' ' + _(u'Timeout while establishing connection'), - "11": _(u'Update failed:') + u' ' + _(u'General error'), - "12": _(u'Update failed:') + u' ' + _(u'Update File Could Not be Saved in Temp Dir') - } - status['text'] = text - updater_thread.status = 0 - updater_thread.resume() - status['status'] = updater_thread.get_update_status() - elif request.method == "GET": - try: - status['status'] = updater_thread.get_update_status() - if status['status'] == -1: - status['status'] = 7 - except Exception: - status['status'] = 11 - return json.dumps(status) + if feature_support['updater']: + if request.method == "POST": + commit = request.form.to_dict() + if "start" in commit and commit['start'] == 'True': + text = { + "1": _(u'Requesting update package'), + "2": _(u'Downloading update package'), + "3": _(u'Unzipping update package'), + "4": _(u'Replacing files'), + "5": _(u'Database connections are closed'), + "6": _(u'Stopping server'), + "7": _(u'Update finished, please press okay and reload page'), + "8": _(u'Update failed:') + u' ' + _(u'HTTP Error'), + "9": _(u'Update failed:') + u' ' + _(u'Connection error'), + "10": _(u'Update failed:') + u' ' + _(u'Timeout while establishing connection'), + "11": _(u'Update failed:') + u' ' + _(u'General error'), + "12": _(u'Update failed:') + u' ' + _(u'Update File Could Not be Saved in Temp Dir') + } + status['text'] = text + updater_thread.status = 0 + updater_thread.resume() + status['status'] = updater_thread.get_update_status() + elif request.method == "GET": + try: + status['status'] = updater_thread.get_update_status() + if status['status'] == -1: + status['status'] = 7 + except Exception: + status['status'] = 11 + return json.dumps(status) + return '' @admi.route('/import_ldap_users') diff --git a/cps/constants.py b/cps/constants.py index e9b9b5b9..ac48a5b8 100644 --- a/cps/constants.py +++ b/cps/constants.py @@ -23,6 +23,7 @@ from collections import namedtuple # if installed via pip this variable is set to true HOME_CONFIG = False +UPDATER_AVAILABLE = True # Base dir is parent of current file, necessary if called from different folder if sys.version_info < (3, 0): diff --git a/cps/server.py b/cps/server.py index b2d9c1b0..3148aed6 100644 --- a/cps/server.py +++ b/cps/server.py @@ -22,6 +22,7 @@ import os import errno import signal import socket +import subprocess try: from gevent.pywsgi import WSGIServer @@ -136,6 +137,64 @@ class WebServer(object): return sock, _readable_listen_address(*address) + + def _get_args_for_reloading(self): + """Determine how the script was executed, and return the args needed + to execute it again in a new process. + Code from https://github.com/pyload/pyload. Author GammaC0de, voulter + """ + rv = [sys.executable] + py_script = sys.argv[0] + args = sys.argv[1:] + # Need to look at main module to determine how it was executed. + __main__ = sys.modules["__main__"] + + # The value of __package__ indicates how Python was called. It may + # not exist if a setuptools script is installed as an egg. It may be + # set incorrectly for entry points created with pip on Windows. + if getattr(__main__, "__package__", None) is None or ( + os.name == "nt" + and __main__.__package__ == "" + and not os.path.exists(py_script) + and os.path.exists(f"{py_script}.exe") + ): + # Executed a file, like "python app.py". + py_script = os.path.abspath(py_script) + + if os.name == "nt": + # Windows entry points have ".exe" extension and should be + # called directly. + if not os.path.exists(py_script) and os.path.exists(f"{py_script}.exe"): + py_script += ".exe" + + if ( + os.path.splitext(sys.executable)[1] == ".exe" + and os.path.splitext(py_script)[1] == ".exe" + ): + rv.pop(0) + + rv.append(py_script) + else: + # Executed a module, like "python -m module". + if sys.argv[0] == "-m": + args = sys.argv + else: + if os.path.isfile(py_script): + # Rewritten by Python from "-m script" to "/path/to/script.py". + py_module = __main__.__package__ + name = os.path.splitext(os.path.basename(py_script))[0] + + if name != "__main__": + py_module += f".{name}" + else: + # Incorrectly rewritten by pydevd debugger from "-m script" to "script". + py_module = py_script + + rv.extend(("-m", py_module.lstrip("."))) + + rv.extend(args) + return rv + def _start_gevent(self): ssl_args = self.ssl_args or {} @@ -199,11 +258,8 @@ class WebServer(object): return True log.info("Performing restart of Calibre-Web") - arguments = list(sys.argv) - arguments.insert(0, sys.executable) - if os.name == 'nt': - arguments = ["\"%s\"" % a for a in arguments] - os.execv(sys.executable, arguments) + args = self._get_args_for_reloading() + subprocess.call(args, close_fds=True) return True def _killServer(self, __, ___): diff --git a/cps/templates/admin.html b/cps/templates/admin.html index 1ef64157..fb0c758f 100644 --- a/cps/templates/admin.html +++ b/cps/templates/admin.html @@ -168,9 +168,11 @@ + {% if feature_support['updater'] %}
{{_('Check for Update')}}
+ {% endif %} diff --git a/cps/updater.py b/cps/updater.py index b720c885..b03a0844 100644 --- a/cps/updater.py +++ b/cps/updater.py @@ -264,7 +264,7 @@ class Updater(threading.Thread): # log_from_thread("Delete file " + item_path) os.remove(item_path) except OSError: - logger.debug("Could not remove: %s", item_path) + log.debug("Could not remove: %s", item_path) shutil.rmtree(source, ignore_errors=True) def is_venv(self): diff --git a/optional-requirements.txt b/optional-requirements.txt index 3f0f1e00..d3e21f5b 100644 --- a/optional-requirements.txt +++ b/optional-requirements.txt @@ -1,7 +1,7 @@ # GDrive Integration google-api-python-client>=1.7.11,<1.8.0 -gevent>=1.2.1,<20.6.0 -greenlet>=0.4.12,<0.4.17 +gevent>20.6.0,<21.2.0 +greenlet>=0.4.17,<1.1.0 httplib2>=0.9.2,<0.18.0 oauth2client>=4.0.0,<4.1.4 uritemplate>=3.0.0,<3.1.0