-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feature(installer): update installer version and improve desktop version launcher #2157
Changes from 32 commits
cf4d0f9
85899f4
531de20
1f61684
b9ed900
82dcfd9
e2c9606
ddcabd2
fad5312
eb510e7
dffd049
07bd40e
cd05210
9c34f70
1d802f8
6786b64
601b456
d6cb772
f53e676
cb7b55b
ea8ec2c
f77e700
afe6c78
a643ac4
bb04028
a067642
a0e1268
92c76ce
609cf9a
4fe01b8
b2246f1
d8e9654
42e6574
ca61bea
2efdeb6
a4c3790
55af4ba
7cb6225
1a02de5
b796a9a
df27b63
ccbae1e
d454c68
d1951e4
fba79d3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ on: | |
branches: | ||
- "master" | ||
- "hotfix/**" | ||
- "feature/update-installer" | ||
|
||
jobs: | ||
binary: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,27 @@ | ||
# -*- mode: python ; coding: utf-8 -*- | ||
from pathlib import Path | ||
from PyInstaller.utils.hooks import collect_dynamic_libs | ||
|
||
block_cipher = None | ||
|
||
# We need to analyze all alembic files to be sure the migration phase works fine | ||
migrations_dir = Path('alembic/versions') | ||
migration_files = [str(f) for f in migrations_dir.iterdir() if f.is_file() and f.suffix == '.py'] | ||
# We need to analyze all alembic files to be sure the migration phase works fine: | ||
# alembic loads version files by their path, so we need to add them as "data" to the package, | ||
# but all the dependencies they use need to be included also, wo we need to perform a | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo: so we need |
||
# dedicated analyse for this. | ||
versions_dir = Path('alembic/versions') | ||
versions_files = [str(f) for f in versions_dir.iterdir() if f.is_file() and f.suffix == '.py'] | ||
alembic_analysis = Analysis(["alembic/env.py"] + versions_files) | ||
|
||
binaries = [('./alembic.ini', './alembic.ini')] + collect_dynamic_libs('tables') | ||
|
||
antares_web_server_a = Analysis(['antarest/gui.py', 'alembic/env.py'] + migration_files, | ||
antares_web_server_a = Analysis(['antarest/gui.py'], | ||
pathex=[], | ||
binaries=binaries, | ||
datas=[('./resources', './resources'), ('./alembic', './alembic')], | ||
binaries=[], | ||
datas=[('./resources', './resources'), ('./alembic', './alembic'), ('./alembic.ini', './')], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why did you add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Before we had this: Now the file is correctly placed at the root |
||
hiddenimports=[ | ||
'cmath', | ||
'antarest.dbmodel', | ||
'plyer.platforms.linux', | ||
'plyer.platforms.linux.notification', | ||
'pythonjsonlogger.jsonlogger', | ||
'tables', | ||
], | ||
hookspath=['extra-hooks'], | ||
hooksconfig={}, | ||
|
@@ -29,8 +31,13 @@ antares_web_server_a = Analysis(['antarest/gui.py', 'alembic/env.py'] + migratio | |
win_private_assemblies=False, | ||
cipher=block_cipher, | ||
noarchive=False) | ||
antares_web_server_pyz = PYZ(antares_web_server_a.pure, antares_web_server_a.zipped_data, | ||
|
||
all_python = antares_web_server_a.pure + alembic_analysis.pure | ||
all_zipped_data = antares_web_server_a.zipped_data + alembic_analysis.zipped_data | ||
|
||
antares_web_server_pyz = PYZ(all_python, all_zipped_data, | ||
cipher=block_cipher) | ||
|
||
antares_web_server_exe = EXE(antares_web_server_pyz, | ||
antares_web_server_a.scripts, | ||
[], | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,27 @@ | ||
# -*- mode: python ; coding: utf-8 -*- | ||
from pathlib import Path | ||
from PyInstaller.utils.hooks import collect_dynamic_libs | ||
|
||
block_cipher = None | ||
|
||
# We need to analyze all alembic files to be sure the migration phase works fine | ||
migrations_dir = Path('alembic/versions') | ||
migration_files = [str(f) for f in migrations_dir.iterdir() if f.is_file() and f.suffix == '.py'] | ||
# We need to analyze all alembic files to be sure the migration phase works fine: | ||
# alembic loads version files by their path, so we need to add them as "data" to the package, | ||
# but all the dependencies they use need to be included also, wo we need to perform a | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same typo |
||
# dedicated analyse for this. | ||
versions_dir = Path('alembic/versions') | ||
versions_files = [str(f) for f in versions_dir.iterdir() if f.is_file() and f.suffix == '.py'] | ||
alembic_analysis = Analysis(["alembic/env.py"] + versions_files) | ||
|
||
binaries = [('./alembic.ini', './alembic.ini')] + collect_dynamic_libs('tables') | ||
|
||
antares_web_server_a = Analysis(['antarest/gui.py', 'alembic/env.py'] + migration_files, | ||
antares_web_server_a = Analysis(['antarest/gui.py'], | ||
pathex=[], | ||
binaries=binaries, | ||
datas=[('./resources', './resources'), ('./alembic', './alembic')], | ||
binaries=[], | ||
datas=[('./resources', './resources'), ('./alembic', './alembic'), ('./alembic.ini', './')], | ||
hiddenimports=[ | ||
'cmath', | ||
'antarest.dbmodel', | ||
'plyer.platforms.win', | ||
'plyer.platforms.win.notification', | ||
'pythonjsonlogger.jsonlogger', | ||
'tables', | ||
], | ||
hookspath=['extra-hooks'], | ||
hooksconfig={}, | ||
|
@@ -29,8 +31,13 @@ antares_web_server_a = Analysis(['antarest/gui.py', 'alembic/env.py'] + migratio | |
win_private_assemblies=False, | ||
cipher=block_cipher, | ||
noarchive=False) | ||
antares_web_server_pyz = PYZ(antares_web_server_a.pure, antares_web_server_a.zipped_data, | ||
|
||
all_python = antares_web_server_a.pure + alembic_analysis.pure | ||
all_zipped_data = antares_web_server_a.zipped_data + alembic_analysis.zipped_data | ||
|
||
antares_web_server_pyz = PYZ(all_python, all_zipped_data, | ||
cipher=block_cipher) | ||
|
||
antares_web_server_exe = EXE(antares_web_server_pyz, | ||
antares_web_server_a.scripts, | ||
[], | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,13 +15,15 @@ | |
import platform | ||
import time | ||
import webbrowser | ||
from dataclasses import dataclass | ||
from multiprocessing import Process | ||
from pathlib import Path | ||
from threading import Thread | ||
|
||
import httpx | ||
import uvicorn | ||
from PyQt5.QtGui import QIcon | ||
from PyQt5.QtWidgets import QAction, QApplication, QMenu, QSystemTrayIcon | ||
from PyQt5.QtGui import QCursor, QIcon | ||
from PyQt5.QtWidgets import QApplication, QMenu, QSystemTrayIcon | ||
|
||
from antarest.core.utils.utils import get_local_path | ||
from antarest.main import fastapi_app, parse_arguments | ||
|
@@ -39,67 +41,139 @@ def run_server(config_file: Path) -> None: | |
uvicorn.run(app, host="127.0.0.1", port=8080) | ||
|
||
|
||
def start_server(config_file: Path) -> Process: | ||
server = multiprocessing.Process( | ||
target=run_server, | ||
args=(config_file,), | ||
) | ||
server.start() | ||
return server | ||
|
||
|
||
def open_app() -> None: | ||
webbrowser.open("http://localhost:8080") | ||
|
||
|
||
def main() -> None: | ||
multiprocessing.freeze_support() | ||
arguments = parse_arguments() | ||
@dataclass(frozen=True) | ||
class AntaresSystrayApp: | ||
""" | ||
Used to keep ownership of root Qt objects. | ||
QMenu can only be owned by QWidgets, but we don't have one. | ||
""" | ||
|
||
app: QApplication | ||
menu: QMenu | ||
|
||
|
||
def create_systray_app() -> AntaresSystrayApp: | ||
""" | ||
Creates the small application that allows to open | ||
the browser or shutdown the server. | ||
""" | ||
app = QApplication([]) | ||
app.setQuitOnLastWindowClosed(False) | ||
|
||
# Adding an icon | ||
icon = QIcon(str(RESOURCE_PATH / "webapp" / "logo16.png")) | ||
# Adding item on the menu bar | ||
tray = QSystemTrayIcon(icon, app) | ||
tray.setToolTip("AntaresWebServer") | ||
|
||
# Creating the options | ||
menu = QMenu() | ||
open_app_action = menu.addAction("Open application") | ||
assert open_app_action is not None | ||
open_app_action.triggered.connect(open_app) | ||
# To quit the app | ||
quit_action = menu.addAction("Quit") | ||
assert quit_action is not None | ||
quit_action.triggered.connect(app.quit) | ||
|
||
# Adding options to the System Tray | ||
def handle_action(reason: int) -> None: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function/design is really weird but I'm far from understanding this file |
||
""" | ||
Shows context menu also on left click | ||
""" | ||
if reason == QSystemTrayIcon.Trigger: # type: ignore | ||
menu = tray.contextMenu() | ||
assert menu is not None | ||
menu.popup(QCursor.pos()) | ||
|
||
tray.setContextMenu(menu) | ||
tray.activated.connect(handle_action) | ||
|
||
tray.setVisible(True) | ||
|
||
return AntaresSystrayApp(app, menu) | ||
|
||
|
||
def monitor_server_process(server: Process, app: QApplication) -> None: | ||
""" | ||
Quits the application when server process ends. | ||
""" | ||
server.join() | ||
app.quit() | ||
|
||
|
||
def setup_exit_application_on_server_end(server: Process, app: QApplication) -> None: | ||
Thread(target=monitor_server_process, args=(server, app)).start() | ||
|
||
|
||
def check_server_started() -> bool: | ||
with contextlib.suppress(httpx.ConnectError): | ||
res = httpx.get("http://localhost:8080/health") | ||
if res.status_code == 200: | ||
return True | ||
return False | ||
|
||
|
||
def wait_for_server_start() -> None: | ||
for _ in range(30, 0, -1): | ||
if check_server_started(): | ||
break | ||
time.sleep(1) | ||
|
||
|
||
def notification_popup(message: str, threaded: bool = True) -> None: | ||
if platform.system() == "Windows": | ||
# noinspection PyPackageRequirements | ||
from win10toast import ToastNotifier # type: ignore | ||
|
||
toaster = ToastNotifier() | ||
toaster.show_toast( | ||
"AntaresWebServer", | ||
"Antares Web Server started, you can manage the application within the systray app", | ||
message, | ||
icon_path=RESOURCE_PATH / "webapp" / "favicon.ico", | ||
threaded=True, | ||
threaded=threaded, | ||
) | ||
else: | ||
from plyer import notification # type: ignore | ||
|
||
notification.notify( | ||
title="AntaresWebServer", | ||
message="Antares Web Server started, you can manage the application within the systray app", | ||
message=message, | ||
app_name="AntaresWebServer", | ||
app_icon=RESOURCE_PATH / "webapp" / "favicon.ico", | ||
app_icon=str(RESOURCE_PATH / "webapp" / "favicon.ico"), | ||
timeout=600, | ||
) | ||
app = QApplication([]) | ||
app.setQuitOnLastWindowClosed(False) | ||
# Adding an icon | ||
icon = QIcon(str(RESOURCE_PATH / "webapp" / "logo16.png")) | ||
# Adding item on the menu bar | ||
tray = QSystemTrayIcon() | ||
tray.setIcon(icon) | ||
tray.setVisible(True) | ||
# Creating the options | ||
menu = QMenu() | ||
open_app_action = QAction("Open application") | ||
menu.addAction(open_app_action) | ||
open_app_action.triggered.connect(open_app) | ||
# To quit the app | ||
quit_action = QAction("Quit") | ||
quit_action.triggered.connect(app.quit) | ||
menu.addAction(quit_action) | ||
# Adding options to the System Tray | ||
tray.setContextMenu(menu) | ||
app.processEvents() | ||
tray.setToolTip("AntaresWebServer") | ||
server = Process( | ||
target=run_server, | ||
args=(arguments.config_file,), | ||
) | ||
server.start() | ||
for _ in range(30, 0, -1): | ||
with contextlib.suppress(httpx.ConnectError): | ||
res = httpx.get("http://localhost:8080") | ||
if res.status_code == 200: | ||
break | ||
time.sleep(1) | ||
app.exec_() | ||
|
||
|
||
def main() -> None: | ||
multiprocessing.freeze_support() | ||
|
||
arguments = parse_arguments() | ||
if check_server_started(): | ||
notification_popup( | ||
"Antares Web Server already running, you can manage the application within the system tray.", threaded=False | ||
) | ||
return | ||
notification_popup("Starting Antares Web Server...") | ||
systray_app = create_systray_app() | ||
server = start_server(arguments.config_file) | ||
setup_exit_application_on_server_end(server, systray_app.app) | ||
wait_for_server_start() | ||
notification_popup("Antares Web Server started, you can manage the application within the system tray.") | ||
systray_app.app.exec_() | ||
server.kill() | ||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be removed or we could add more generic branch name such as
installer/**
ordeploy/**