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:
chrome-storm-c442
2026-03-01 09:00:27 -05:00
parent c23eb36dcc
commit 9393134593
8 changed files with 974 additions and 1 deletions

View File

@@ -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()