v1.8.78: auto-updater — Gitea releases check, download, apply
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
128
gui/app.py
128
gui/app.py
@@ -8,6 +8,7 @@ from tkinter import messagebox
|
||||
|
||||
from core.server_store import ServerStore
|
||||
from core.status_checker import StatusChecker
|
||||
from core.updater import UpdateChecker
|
||||
from core import i18n
|
||||
from core.i18n import t, LANGUAGES
|
||||
from core.icons import icon, TAB_ICONS
|
||||
@@ -83,6 +84,7 @@ class App(ctk.CTk):
|
||||
self.store = ServerStore()
|
||||
self.checker = StatusChecker(self.store)
|
||||
self.session_pool = SessionPool(max_sessions=5) # Create session pool
|
||||
self.updater = UpdateChecker(self.store, gui_callback=self._on_update_event)
|
||||
|
||||
# Restore saved window geometry or use default
|
||||
saved_geo = self.store._window_geometry
|
||||
@@ -99,6 +101,9 @@ class App(ctk.CTk):
|
||||
self.checker.start()
|
||||
self.checker.check_all_now()
|
||||
|
||||
# Auto-updater
|
||||
self.updater.start()
|
||||
|
||||
# Fix Ctrl+V/C/A/X for non-Latin keyboard layouts (e.g. Russian)
|
||||
# Tkinter maps <<Paste>> to <Control-v> by keysym, which fails when
|
||||
# the layout produces non-Latin characters. This fix uses keycodes instead.
|
||||
@@ -142,6 +147,14 @@ class App(ctk.CTk):
|
||||
)
|
||||
self.lang_menu.pack(side="right", padx=(5, 0))
|
||||
|
||||
# Check Updates button
|
||||
self._update_check_btn = ctk.CTkButton(
|
||||
header_bar, text="\u21bb", width=30, height=30,
|
||||
corner_radius=15, fg_color="#6b7280", hover_color="#4b5563",
|
||||
command=self._check_updates_manual,
|
||||
)
|
||||
self._update_check_btn.pack(side="right", padx=(5, 0))
|
||||
|
||||
# About button
|
||||
self.about_btn = ctk.CTkButton(
|
||||
header_bar, text="ⓘ", width=30, height=30,
|
||||
@@ -150,6 +163,11 @@ class App(ctk.CTk):
|
||||
)
|
||||
self.about_btn.pack(side="right", padx=(5, 5))
|
||||
|
||||
# Update banner (hidden by default)
|
||||
self._update_banner = None
|
||||
self._pending_update_info = None
|
||||
self._pending_download_path = None
|
||||
|
||||
# Initialize tab tracking
|
||||
self.tabview = None
|
||||
self._tab_keys = []
|
||||
@@ -316,6 +334,115 @@ class App(ctk.CTk):
|
||||
def _show_about(self):
|
||||
AboutDialog(self)
|
||||
|
||||
# ── Update handling ─────────────────────────────────
|
||||
|
||||
def _on_update_event(self, event_type: str, info: dict, path: str = None):
|
||||
"""Called from updater thread — schedule GUI work on main thread."""
|
||||
self.after(0, lambda: self._handle_update_event(event_type, info, path))
|
||||
|
||||
def _handle_update_event(self, event_type: str, info: dict, path: str = None):
|
||||
"""Handle update events on the main thread."""
|
||||
self._pending_update_info = info
|
||||
self._pending_download_path = path
|
||||
|
||||
if event_type == "auto_apply":
|
||||
# Full-auto mode: apply immediately
|
||||
if self.updater.apply_update(path):
|
||||
self.destroy()
|
||||
return
|
||||
|
||||
# Show banner
|
||||
self._show_update_banner(info, downloaded=(path is not None))
|
||||
|
||||
def _show_update_banner(self, info: dict, downloaded: bool = False):
|
||||
"""Show/update the update banner."""
|
||||
from gui.update_dialog import UpdateBanner
|
||||
|
||||
if self._update_banner is not None:
|
||||
try:
|
||||
self._update_banner.destroy()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self._update_banner = UpdateBanner(
|
||||
self._main_frame,
|
||||
on_update=self._show_update_dialog,
|
||||
on_skip=lambda: self._skip_update(info["version"]),
|
||||
on_dismiss=self._dismiss_banner,
|
||||
)
|
||||
self._update_banner.set_info(info["version"], downloaded=downloaded)
|
||||
# Pack banner between header and tabview
|
||||
self._update_banner.pack(fill="x", padx=10, pady=(4, 0), before=self.tabview)
|
||||
|
||||
def _show_update_dialog(self):
|
||||
"""Open the update dialog."""
|
||||
from gui.update_dialog import UpdateDialog
|
||||
|
||||
if not self._pending_update_info:
|
||||
return
|
||||
|
||||
import sys
|
||||
if not getattr(sys, "frozen", False):
|
||||
from tkinter import messagebox
|
||||
messagebox.showinfo(
|
||||
t("update_available_title"),
|
||||
t("update_not_frozen"),
|
||||
)
|
||||
return
|
||||
|
||||
UpdateDialog(
|
||||
self,
|
||||
self._pending_update_info,
|
||||
downloaded_path=self._pending_download_path,
|
||||
on_install=self._apply_update,
|
||||
on_skip=self._skip_update,
|
||||
)
|
||||
|
||||
def _apply_update(self, path: str):
|
||||
"""Apply downloaded update."""
|
||||
if self.updater.apply_update(path):
|
||||
self.destroy()
|
||||
|
||||
def _skip_update(self, version: str):
|
||||
"""Skip this version."""
|
||||
self.store.set_skip_version(version)
|
||||
self._dismiss_banner()
|
||||
|
||||
def _dismiss_banner(self):
|
||||
if self._update_banner:
|
||||
try:
|
||||
self._update_banner.pack_forget()
|
||||
self._update_banner.destroy()
|
||||
except Exception:
|
||||
pass
|
||||
self._update_banner = None
|
||||
|
||||
def _check_updates_manual(self):
|
||||
"""Manual check for updates (button click)."""
|
||||
import threading
|
||||
from tkinter import messagebox
|
||||
|
||||
self._update_check_btn.configure(state="disabled")
|
||||
|
||||
def _check():
|
||||
info = self.updater.check_now()
|
||||
self.after(0, lambda: self._manual_check_done(info))
|
||||
|
||||
threading.Thread(target=_check, daemon=True).start()
|
||||
|
||||
def _manual_check_done(self, info):
|
||||
self._update_check_btn.configure(state="normal")
|
||||
if info:
|
||||
self._pending_update_info = info
|
||||
self._pending_download_path = None
|
||||
self._show_update_banner(info)
|
||||
else:
|
||||
from tkinter import messagebox
|
||||
messagebox.showinfo(
|
||||
t("update_check"),
|
||||
t("update_no_updates"),
|
||||
)
|
||||
|
||||
def _get_current_tab_key(self) -> str:
|
||||
"""Get the i18n key of the currently active tab."""
|
||||
try:
|
||||
@@ -514,4 +641,5 @@ class App(ctk.CTk):
|
||||
# Disconnect all sessions before closing
|
||||
self.session_pool.disconnect_all()
|
||||
self.checker.stop()
|
||||
self.updater.stop()
|
||||
self.destroy()
|
||||
|
||||
Reference in New Issue
Block a user