mirror of
https://github.com/aclist/dztui.git
synced 2026-10-31 15:12:48 +01:00
feat: auto update
This commit is contained in:
1 parent
80c58be3be
commit
c063059c19
10 files changed
+98
-27
No files matched your search
@@ -15,5 +15,5 @@ class UserPrefs:
|
|||||||
coords: Union["Coords", None]
|
coords: Union["Coords", None]
|
||||||
version: str
|
version: str
|
||||||
paths: "Xdg"
|
paths: "Xdg"
|
||||||
update_available: bool
|
latest_release: str | None
|
||||||
use_miles: bool
|
use_miles: bool
|
||||||
@@ -73,3 +73,7 @@ UBUNTU_STEAM_PATH = ".steam/steam"
|
|||||||
DEBIAN_STEAM_PATH = ".steam/debian-installation"
|
DEBIAN_STEAM_PATH = ".steam/debian-installation"
|
||||||
|
|
||||||
LOG_FILTERS = ("CRITICAL", "WARNING", "INFO", "DEBUG")
|
LOG_FILTERS = ("CRITICAL", "WARNING", "INFO", "DEBUG")
|
||||||
|
|
||||||
|
TMP_PATH = "/tmp"
|
||||||
|
TMP_TARBALL = "/tmp/dzgui.tar.gz"
|
||||||
|
TMP_EXE = "/tmp/dzgui"
|
||||||
+11
-9
@@ -9,28 +9,30 @@ from dzgui.const.endpoints import GITHUB_RELEASES, CODEBERG_RELEASES
|
|||||||
logger = logging.getLogger(APP_NAME)
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
|
||||||
|
|
||||||
def get_latest_release() -> str | None:
|
def get_latest_release() -> tuple[str, str] | tuple[None, None]:
|
||||||
tag = None
|
tag = None
|
||||||
|
url = None
|
||||||
# TODO: check order; github often has gateway errors
|
# TODO: check order; github often has gateway errors
|
||||||
for url in [GITHUB_RELEASES, CODEBERG_RELEASES]:
|
for url in [GITHUB_RELEASES, CODEBERG_RELEASES]:
|
||||||
try:
|
try:
|
||||||
res = requests.get(url, timeout=REQUEST_TIMEOUT)
|
res = requests.get(url, timeout=REQUEST_TIMEOUT)
|
||||||
if res.status_code == 200:
|
if res.status_code == 200:
|
||||||
tag = res.json()["tag_name"]
|
tag = str(res.json()["tag_name"])
|
||||||
|
url = str(res.json()["assets"][0]["browser_download_url"])
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.critical(e)
|
logger.critical(e)
|
||||||
continue
|
continue
|
||||||
return tag
|
return tag, url
|
||||||
|
|
||||||
|
|
||||||
def check_updates(version: str) -> bool:
|
def check_updates(version: str) -> str | None:
|
||||||
try:
|
try:
|
||||||
latest = get_latest_release()
|
latest, url = get_latest_release()
|
||||||
if latest is None:
|
if latest is None:
|
||||||
return False
|
return None
|
||||||
if Version(version) >= Version(latest):
|
if Version(version) >= Version(latest):
|
||||||
return False
|
return None
|
||||||
return True
|
return url
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return None
|
||||||
+2
-2
@@ -116,7 +116,7 @@ def main() -> None:
|
|||||||
with open(XDG.debug, "w") as f:
|
with open(XDG.debug, "w") as f:
|
||||||
f.truncate(0)
|
f.truncate(0)
|
||||||
|
|
||||||
update_available = check_updates(version)
|
latest_release = check_updates(version)
|
||||||
|
|
||||||
if _is_steam_deck is False:
|
if _is_steam_deck is False:
|
||||||
# TODO: sudo escalation dialog
|
# TODO: sudo escalation dialog
|
||||||
@@ -142,7 +142,7 @@ def main() -> None:
|
|||||||
coords=local_coords,
|
coords=local_coords,
|
||||||
version=version,
|
version=version,
|
||||||
paths=XDG,
|
paths=XDG,
|
||||||
update_available=update_available,
|
latest_release=latest_release,
|
||||||
use_miles=use_miles,
|
use_miles=use_miles,
|
||||||
)
|
)
|
||||||
print(boot.all_ok)
|
print(boot.all_ok)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ def call_on_thread(
|
|||||||
self = args[0]
|
self = args[0]
|
||||||
stored = StoredFunc(func, *args, **kwargs)
|
stored = StoredFunc(func, *args, **kwargs)
|
||||||
if not hasattr(self, "thread_man"):
|
if not hasattr(self, "thread_man"):
|
||||||
raise AttributeError
|
raise AttributeError(f"Object '{self}' has no attribute 'thread_man'")
|
||||||
if type(self.thread_man) is not ThreadingManager:
|
if type(self.thread_man) is not ThreadingManager:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
"Attribute 'thread_man' must be of type 'ThreadingManager'"
|
"Attribute 'thread_man' must be of type 'ThreadingManager'"
|
||||||
|
|||||||
@@ -1,2 +1,6 @@
|
|||||||
waiting_for_launch = "Waiting for DayZ to launch"
|
waiting_for_launch = "Waiting for DayZ to launch"
|
||||||
waiting_for_mods = "Waiting for Steam to update mods"
|
waiting_for_mods = "Waiting for Steam to update mods"
|
||||||
|
|
||||||
|
fetching_update = "Fetching update"
|
||||||
|
failed_to_update = "Failed to update DZGUI executable"
|
||||||
|
update_success = "Updated successfully. Please exit and relaunch."
|
||||||
@@ -38,6 +38,7 @@ input_required = "User input required"
|
|||||||
confirm = "Confirmation"
|
confirm = "Confirmation"
|
||||||
notice = "Notice"
|
notice = "Notice"
|
||||||
wait = "Please wait"
|
wait = "Please wait"
|
||||||
|
restart_required = "App relaunch required"
|
||||||
|
|
||||||
# Wait dialogs
|
# Wait dialogs
|
||||||
ping = "Ping"
|
ping = "Ping"
|
||||||
|
|||||||
@@ -1,14 +1,31 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import tarfile
|
||||||
|
|
||||||
from typing import Literal, TYPE_CHECKING
|
from typing import Literal, TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.const.constants import NO_EXPAND, NO_FILL, FILL, NO_PADDING
|
from dzgui.const.constants import (
|
||||||
from dzgui.const.endpoints import GITHUB_USER_RELEASES
|
APP_NAME,
|
||||||
|
NO_EXPAND,
|
||||||
|
NO_FILL,
|
||||||
|
FILL,
|
||||||
|
NO_PADDING,
|
||||||
|
TMP_EXE,
|
||||||
|
TMP_PATH,
|
||||||
|
TMP_TARBALL,
|
||||||
|
)
|
||||||
from dzgui.const.enum import ServerTab
|
from dzgui.const.enum import ServerTab
|
||||||
|
from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager
|
||||||
|
from dzgui.strings import dialogs
|
||||||
from dzgui.util.clip import copy_clipboard
|
from dzgui.util.clip import copy_clipboard
|
||||||
from dzgui.util.open_links import open_link_by_url
|
|
||||||
from dzgui.views.components.buttonbox import ButtonBox
|
from dzgui.views.components.buttonbox import ButtonBox
|
||||||
from dzgui.views.components.filter_panel import FilterPanel
|
from dzgui.views.components.filter_panel import FilterPanel
|
||||||
from dzgui.views.components.mod_panel import ModSelectionPanel
|
from dzgui.views.components.mod_panel import ModSelectionPanel
|
||||||
from dzgui.views.components.buttons import IconTextButton, RefreshButton, KeysButton
|
from dzgui.views.components.buttons import IconTextButton, RefreshButton, KeysButton
|
||||||
|
from dzgui.views.dialogs.generic import ExceptionDialog, QuitDialog
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
|
|
||||||
@@ -20,11 +37,14 @@ if TYPE_CHECKING:
|
|||||||
from dzgui.controllers.emitter import Emitter
|
from dzgui.controllers.emitter import Emitter
|
||||||
from dzgui.views.trees.tree_servers import ServerTreeView
|
from dzgui.views.trees.tree_servers import ServerTreeView
|
||||||
|
|
||||||
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
|
||||||
|
|
||||||
class RightPanel(Gtk.Box):
|
class RightPanel(Gtk.Box):
|
||||||
def __init__(self, controller: "Controller"):
|
def __init__(self, controller: "Controller"):
|
||||||
super().__init__(spacing=6, orientation=Gtk.Orientation.VERTICAL)
|
super().__init__(spacing=6, orientation=Gtk.Orientation.VERTICAL)
|
||||||
|
|
||||||
|
self.thread_man = ThreadingManager(controller)
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.controller.register_widget("right_panel", self)
|
self.controller.register_widget("right_panel", self)
|
||||||
|
|
||||||
@@ -44,7 +64,7 @@ class RightPanel(Gtk.Box):
|
|||||||
|
|
||||||
prefs = self.controller.get_prefs()
|
prefs = self.controller.get_prefs()
|
||||||
version = prefs.version
|
version = prefs.version
|
||||||
update = prefs.update_available
|
update = prefs.latest_release
|
||||||
|
|
||||||
self.version_label = Gtk.Label(
|
self.version_label = Gtk.Label(
|
||||||
label=version,
|
label=version,
|
||||||
@@ -64,7 +84,7 @@ class RightPanel(Gtk.Box):
|
|||||||
|
|
||||||
self.pack_start(self.sel_panel, NO_EXPAND, NO_FILL, NO_PADDING)
|
self.pack_start(self.sel_panel, NO_EXPAND, NO_FILL, NO_PADDING)
|
||||||
|
|
||||||
self.version_button = IconTextButton(
|
self.update_button = IconTextButton(
|
||||||
"dialog-information-symbolic", label="Updates available"
|
"dialog-information-symbolic", label="Updates available"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -74,15 +94,54 @@ class RightPanel(Gtk.Box):
|
|||||||
valign=Gtk.Align.END,
|
valign=Gtk.Align.END,
|
||||||
spacing=10,
|
spacing=10,
|
||||||
)
|
)
|
||||||
if update:
|
if update is not None:
|
||||||
self.version_button.set_halign(Gtk.Align.END)
|
self.update_button.set_halign(Gtk.Align.END)
|
||||||
self.gutter_box.add(self.version_button)
|
self.gutter_box.add(self.update_button)
|
||||||
self.version_button.connect("clicked", self._on_version_button_clicked)
|
self.update_button.connect(
|
||||||
|
"clicked", self._on_update_button_clicked, update
|
||||||
|
)
|
||||||
self.gutter_box.add(eb)
|
self.gutter_box.add(eb)
|
||||||
self.pack_start(self.gutter_box, NO_EXPAND, FILL, NO_PADDING)
|
self.pack_start(self.gutter_box, NO_EXPAND, FILL, NO_PADDING)
|
||||||
|
|
||||||
def _on_version_button_clicked(self, button: Gtk.Button) -> None:
|
def _on_update_success(self) -> None:
|
||||||
open_link_by_url(GITHUB_USER_RELEASES)
|
msg = dialogs.update_success
|
||||||
|
dialog = QuitDialog(self.controller, msg)
|
||||||
|
dialog.run()
|
||||||
|
|
||||||
|
def _on_update_failure(self, msg: str) -> None:
|
||||||
|
dialog = ExceptionDialog(self.controller, msg)
|
||||||
|
dialog.run()
|
||||||
|
|
||||||
|
@call_on_thread(dialogs.fetching_update)
|
||||||
|
def _on_update_button_clicked(self, button: Gtk.Button, url: str) -> None:
|
||||||
|
try:
|
||||||
|
res = requests.get(url)
|
||||||
|
if res.status_code == 200:
|
||||||
|
with open(TMP_TARBALL, "wb") as file:
|
||||||
|
file.write(res.content)
|
||||||
|
with tarfile.open(TMP_TARBALL) as tar:
|
||||||
|
tar.extractall(TMP_PATH)
|
||||||
|
|
||||||
|
exe_path = os.getenv("PYAPP")
|
||||||
|
if exe_path is None:
|
||||||
|
msg = "Failed to find DZGUI launch executable"
|
||||||
|
func = StoredFunc(self._on_update_failure, msg)
|
||||||
|
self.thread_man.set_cleanup_func(func, destroy_first=True)
|
||||||
|
return
|
||||||
|
shutil.move(TMP_EXE, exe_path)
|
||||||
|
|
||||||
|
proc = subprocess.run([exe_path, "self", "restore"])
|
||||||
|
if proc.returncode == 0:
|
||||||
|
func = StoredFunc(self._on_update_success)
|
||||||
|
self.thread_man.set_cleanup_func(func, destroy_first=True)
|
||||||
|
else:
|
||||||
|
msg = dialogs.failed_to_update
|
||||||
|
func = StoredFunc(self._on_update_failure, msg)
|
||||||
|
self.thread_man.set_cleanup_func(func, destroy_first=True)
|
||||||
|
except Exception as e:
|
||||||
|
func = StoredFunc(self._on_update_failure, e)
|
||||||
|
self.thread_man.set_cleanup_func(func, destroy_first=True)
|
||||||
|
logger.warning(e)
|
||||||
|
|
||||||
def _on_server_page_changed(
|
def _on_server_page_changed(
|
||||||
self, emitter: "Emitter", page: "ServerTreeView"
|
self, emitter: "Emitter", page: "ServerTreeView"
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ class NotifyDialog(GenericDialog):
|
|||||||
controller=controller,
|
controller=controller,
|
||||||
text=strings.notice,
|
text=strings.notice,
|
||||||
mtype=Gtk.MessageType.INFO,
|
mtype=Gtk.MessageType.INFO,
|
||||||
buttons=Gtk.ButtonsType.OK_CANCEL,
|
buttons=Gtk.ButtonsType.OK,
|
||||||
secondary=secondary,
|
secondary=secondary,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -143,7 +143,7 @@ class QuitDialog(GenericDialog):
|
|||||||
def __init__(self, controller: "Controller", secondary: str):
|
def __init__(self, controller: "Controller", secondary: str):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
controller=controller,
|
controller=controller,
|
||||||
text=strings.wait,
|
text=strings.restart_required,
|
||||||
mtype=Gtk.MessageType.INFO,
|
mtype=Gtk.MessageType.INFO,
|
||||||
buttons=Gtk.ButtonsType.NONE,
|
buttons=Gtk.ButtonsType.NONE,
|
||||||
secondary=secondary,
|
secondary=secondary,
|
||||||
|
|||||||
+2
-1
@@ -30,8 +30,9 @@ env["PYAPP_PROJECT_VERSION"] = version
|
|||||||
env["PYAPP_PROJECT_NAME"] = APP_NAME_LOWER
|
env["PYAPP_PROJECT_NAME"] = APP_NAME_LOWER
|
||||||
env["PYAPP_EXEC_SPEC"] = entrypoint
|
env["PYAPP_EXEC_SPEC"] = entrypoint
|
||||||
env["PYAPP_PROJECT_PATH"] = wheel
|
env["PYAPP_PROJECT_PATH"] = wheel
|
||||||
env["PYAPP_DISTRIBUTION_EMBED"] = "1"
|
env["PYAPP_DISTRIBUTION_EMBED"] = "true"
|
||||||
env["PYAPP_PYTHON_VERSION"] = "3.13"
|
env["PYAPP_PYTHON_VERSION"] = "3.13"
|
||||||
|
env["PYAPP_PASS_LOCATION"] = "true"
|
||||||
|
|
||||||
proc = subprocess.run(["cargo", "build", "--release"], env=env, cwd=pyapp_dir)
|
proc = subprocess.run(["cargo", "build", "--release"], env=env, cwd=pyapp_dir)
|
||||||
if proc.returncode == 0:
|
if proc.returncode == 0:
|
||||||
|
|||||||
Reference in new issue
Block a user