Add auto-update feature with Gitea release checking
- Add UpdateChecker class to query Gitea API for latest releases - Show update dialog with release notes when new version available - Open browser to release page for download (handles large files) - Allow users to skip specific versions or defer updates - Add "Check for Updates Now" button in settings - Check automatically on startup (respects 24-hour interval) - Pre-configured for repo.anhonesthost.net/streamer-tools Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -133,6 +133,15 @@ class Config:
|
||||
'max_lines': 100,
|
||||
'font_size': 12,
|
||||
'theme': 'dark'
|
||||
},
|
||||
'updates': {
|
||||
'auto_check': True,
|
||||
'gitea_url': 'https://repo.anhonesthost.net',
|
||||
'owner': 'streamer-tools',
|
||||
'repo': 'local-transcription',
|
||||
'skipped_versions': [],
|
||||
'last_check': '',
|
||||
'check_interval_hours': 24
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
129
client/update_checker.py
Normal file
129
client/update_checker.py
Normal file
@@ -0,0 +1,129 @@
|
||||
"""Update checker for Local Transcription - checks Gitea for new releases."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Optional, Callable
|
||||
import threading
|
||||
import requests
|
||||
from packaging import version
|
||||
|
||||
|
||||
@dataclass
|
||||
class ReleaseInfo:
|
||||
"""Information about a release."""
|
||||
version: str # Parsed version without 'v' prefix (e.g., "1.4.0")
|
||||
tag_name: str # Original tag name (e.g., "v1.4.0")
|
||||
release_notes: str # Release notes / body markdown
|
||||
download_url: str # Browser URL for release page (html_url)
|
||||
|
||||
|
||||
class UpdateChecker:
|
||||
"""Checks for updates from a Gitea server."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
current_version: str,
|
||||
gitea_url: str = "",
|
||||
owner: str = "",
|
||||
repo: str = "",
|
||||
timeout: int = 10
|
||||
):
|
||||
"""
|
||||
Initialize the update checker.
|
||||
|
||||
Args:
|
||||
current_version: Current application version (e.g., "1.3.1")
|
||||
gitea_url: Base URL of the Gitea server (e.g., "https://git.example.com")
|
||||
owner: Repository owner/organization name
|
||||
repo: Repository name
|
||||
timeout: Request timeout in seconds
|
||||
"""
|
||||
self.current_version = current_version
|
||||
self.gitea_url = gitea_url.rstrip('/') if gitea_url else ""
|
||||
self.owner = owner
|
||||
self.repo = repo
|
||||
self.timeout = timeout
|
||||
|
||||
def is_configured(self) -> bool:
|
||||
"""Check if update checking is properly configured."""
|
||||
return bool(self.gitea_url and self.owner and self.repo)
|
||||
|
||||
def check_for_update(self) -> tuple[Optional[ReleaseInfo], Optional[str]]:
|
||||
"""
|
||||
Check for updates synchronously.
|
||||
|
||||
Returns:
|
||||
Tuple of (ReleaseInfo or None, error_message or None)
|
||||
- If update available: (ReleaseInfo, None)
|
||||
- If no update: (None, None)
|
||||
- If error: (None, error_message)
|
||||
"""
|
||||
if not self.is_configured():
|
||||
return None, "Update checking not configured"
|
||||
|
||||
try:
|
||||
# Query Gitea API for latest release
|
||||
api_url = f"{self.gitea_url}/api/v1/repos/{self.owner}/{self.repo}/releases/latest"
|
||||
response = requests.get(api_url, timeout=self.timeout)
|
||||
|
||||
if response.status_code == 404:
|
||||
return None, "No releases found"
|
||||
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
# Parse version from tag name (strip 'v' prefix if present)
|
||||
tag_name = data.get('tag_name', '')
|
||||
release_version = tag_name.lstrip('v')
|
||||
|
||||
if not release_version:
|
||||
return None, "Invalid release tag"
|
||||
|
||||
# Compare versions using packaging library
|
||||
try:
|
||||
current_ver = version.parse(self.current_version)
|
||||
release_ver = version.parse(release_version)
|
||||
except version.InvalidVersion as e:
|
||||
return None, f"Invalid version format: {e}"
|
||||
|
||||
# Check if release is newer
|
||||
if release_ver > current_ver:
|
||||
release_info = ReleaseInfo(
|
||||
version=release_version,
|
||||
tag_name=tag_name,
|
||||
release_notes=data.get('body', ''),
|
||||
download_url=data.get('html_url', '')
|
||||
)
|
||||
return release_info, None
|
||||
else:
|
||||
return None, None # No update available
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
return None, "Connection timed out"
|
||||
except requests.exceptions.ConnectionError:
|
||||
return None, "Could not connect to server"
|
||||
except requests.exceptions.RequestException as e:
|
||||
return None, f"Request failed: {e}"
|
||||
except Exception as e:
|
||||
return None, f"Error checking for updates: {e}"
|
||||
|
||||
def check_for_update_async(
|
||||
self,
|
||||
callback: Callable[[Optional[ReleaseInfo], Optional[str]], None]
|
||||
) -> threading.Thread:
|
||||
"""
|
||||
Check for updates asynchronously in a background thread.
|
||||
|
||||
Args:
|
||||
callback: Function to call with (ReleaseInfo or None, error_message or None)
|
||||
|
||||
Returns:
|
||||
The started thread
|
||||
"""
|
||||
def worker():
|
||||
result, error = self.check_for_update()
|
||||
callback(result, error)
|
||||
|
||||
thread = threading.Thread(target=worker, daemon=True)
|
||||
thread.start()
|
||||
return thread
|
||||
Reference in New Issue
Block a user