diff --git a/conftest.py b/conftest.py index e670713..2f2451b 100644 --- a/conftest.py +++ b/conftest.py @@ -132,7 +132,7 @@ def webapp(): """Try to connect to the webapp server.""" try: resp = urllib.request.urlopen( app.url_for("ping") ).read() - assert resp == b"pong" + assert resp.startswith( b"pong: " ) return True except URLError: return False diff --git a/vasl_templates/main.py b/vasl_templates/main.py index a964897..ce77393 100755 --- a/vasl_templates/main.py +++ b/vasl_templates/main.py @@ -5,9 +5,11 @@ import sys import os import os.path import threading +import time import traceback import logging import urllib.request +from urllib.error import URLError import PyQt5.QtWebEngineWidgets from PyQt5.QtWidgets import QApplication, QMessageBox @@ -22,6 +24,8 @@ qt_app = QApplication( sys.argv ) app_settings = None +_webapp_error = None # nb: this needs to be global :shrug: + # --------------------------------------------------------------------- _QT_LOGGING_LEVELS = { @@ -127,28 +131,57 @@ def _do_main( template_pack, default_scenario, remote_debugging, debug ): #pylin import flask.cli flask.cli.show_server_banner = lambda *args: None - # see if we can connect to the webapp server - port = webapp.config["FLASK_PORT_NO"] - url = "http://localhost:{}/ping".format( port ) - try: - resp = urllib.request.urlopen( url ).read() - except: #pylint: disable=bare-except - resp = None - if resp: - raise SimpleError( "The application is already running." ) - # start the webapp server + port = webapp.config[ "FLASK_PORT_NO" ] def webapp_thread(): """Run the webapp server.""" try: webapp.run( host="localhost", port=port, use_reloader=False ) - except Exception as ex: + except Exception as ex: #pylint: disable=broad-except logging.critical( "WEBAPP SERVER EXCEPTION: %s", ex ) logging.critical( traceback.format_exc() ) - raise + # NOTE: We pass the exception to the GUI thread, where it can be shown to the user. + global _webapp_error + _webapp_error = ex thread = threading.Thread( target=webapp_thread ) + # FUDGE! If we detect another instance, we hang on Windows after reporting the error. Running the webapp + # in a daemon thread makes the problem go away - you would think the thread would terminate, since it wouldn't + # be able to listen on the same server port - but I guess not :-/ + thread.daemon = True thread.start() + # NOTE: We want to detect if another instance of the program is already running, but we can't simply + # try to connect to the webapp, since we can't tell the difference between connecting to the webapp + # we just started above, and an already-running instance. We handle this by assigning each instance + # a unique ID, which lets us figure out if we've connected to ourself, or another instance. + from vasl_templates.webapp.main import INSTANCE_ID + + # wait for the webapp server to start + while True: + if _webapp_error: + break + try: + url = "http://localhost:{}/ping".format( port ) + resp = urllib.request.urlopen( url ).read().decode( "utf-8" ) + # we got a response - figure out if we connected to ourself or another instance + if resp[:6] != "pong: ": + raise SimpleError( "Unexpected server check response: {}".format( resp ) ) + if resp[6:] == INSTANCE_ID: + break + else: + from vasl_templates.webapp.config.constants import APP_NAME + QMessageBox.warning( None, APP_NAME, "The program is already running." ) + return -1 + except URLError: + # no response - the webapp server is probably still starting up + time.sleep( 0.25 ) + continue + except Exception as ex: #pylint: disable=broad-except + raise ex + if _webapp_error: + # the webapp server didn't start up - re-raise the error in this thread + raise _webapp_error #pylint: disable=raising-bad-type + # check if we should disable OpenGL # Using the QWebEngineView crashes on Windows 7 in a VM. It uses OpenGL, which is # apparently not well supported on Windows, and is dependent on the graphics card driver: diff --git a/vasl_templates/webapp/main.py b/vasl_templates/webapp/main.py index 4d232e2..dbd9b2b 100644 --- a/vasl_templates/webapp/main.py +++ b/vasl_templates/webapp/main.py @@ -2,6 +2,7 @@ import os import json +import uuid import logging from flask import request, render_template, jsonify, send_file, redirect, url_for, abort @@ -12,6 +13,9 @@ import vasl_templates.webapp.config.constants from vasl_templates.webapp.config.constants import BASE_DIR, DATA_DIR from vasl_templates.webapp import globvars +# NOTE: This is used to stop multiple instances of the program from running (see main.py in the desktop app). +INSTANCE_ID = uuid.uuid4().hex + startup_msg_store = MsgStore() # store messages generated during startup _check_versions = True @@ -159,7 +163,7 @@ def get_default_scenario(): @app.route( "/ping" ) def ping(): """Let the caller know we're alive.""" - return "pong" + return "pong: {}".format( INSTANCE_ID ) # ---------------------------------------------------------------------