Refactor configuration management: migrate to per-user storage, update encryption handling, and enhance versioning in CI
This commit is contained in:
127
utils/update_checker.py
Normal file
127
utils/update_checker.py
Normal file
@@ -0,0 +1,127 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
import requests
|
||||
from packaging.version import InvalidVersion, Version
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class UpdateInfo:
|
||||
current_version: str
|
||||
latest_version: str
|
||||
latest_tag: str
|
||||
html_url: Optional[str]
|
||||
|
||||
@property
|
||||
def update_available(self) -> bool:
|
||||
try:
|
||||
return Version(self.latest_version) > Version(self.current_version)
|
||||
except InvalidVersion:
|
||||
return False
|
||||
|
||||
|
||||
class UpdateCheckError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def _normalize_tag_to_version(tag: str) -> str:
|
||||
tag = (tag or "").strip()
|
||||
if tag.lower().startswith("v"):
|
||||
tag = tag[1:]
|
||||
return tag
|
||||
|
||||
|
||||
def get_current_version() -> str:
|
||||
# Prefer an explicit env override (useful for ad-hoc builds)
|
||||
import os
|
||||
|
||||
env_ver = os.getenv("IMAGE_PROCESSOR_VERSION")
|
||||
if env_ver:
|
||||
return env_ver.strip()
|
||||
|
||||
# Prefer version.py in repo / bundled app
|
||||
try:
|
||||
from version import __version__ # type: ignore
|
||||
|
||||
return str(__version__).strip()
|
||||
except Exception:
|
||||
return "0.0.0.dev0"
|
||||
|
||||
|
||||
def _github_get_json(url: str, timeout_seconds: float = 10.0) -> dict:
|
||||
headers = {
|
||||
"Accept": "application/vnd.github+json",
|
||||
"User-Agent": "images_py-update-checker",
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.get(url, headers=headers, timeout=timeout_seconds)
|
||||
except requests.RequestException as exc:
|
||||
raise UpdateCheckError(f"GitHub request failed: {exc}") from exc
|
||||
|
||||
# Rate-limit or forbidden
|
||||
if response.status_code == 403:
|
||||
remaining = response.headers.get("X-RateLimit-Remaining")
|
||||
if remaining == "0":
|
||||
raise UpdateCheckError("GitHub API rate limit reached. Try again later.")
|
||||
|
||||
if response.status_code >= 400:
|
||||
raise UpdateCheckError(f"GitHub API error {response.status_code}: {response.text[:200]}")
|
||||
|
||||
try:
|
||||
return response.json()
|
||||
except ValueError as exc:
|
||||
raise UpdateCheckError("GitHub API returned invalid JSON") from exc
|
||||
|
||||
|
||||
def get_latest_github_release(owner: str, repo: str) -> tuple[str, str, Optional[str]]:
|
||||
"""Returns (latest_version, latest_tag, html_url).
|
||||
|
||||
Uses releases/latest first; falls back to tags if no releases exist.
|
||||
"""
|
||||
|
||||
releases_url = f"https://api.github.com/repos/{owner}/{repo}/releases/latest"
|
||||
try:
|
||||
payload = _github_get_json(releases_url)
|
||||
tag = str(payload.get("tag_name") or "").strip()
|
||||
html_url = payload.get("html_url")
|
||||
version = _normalize_tag_to_version(tag)
|
||||
if version:
|
||||
return version, tag, html_url
|
||||
except UpdateCheckError:
|
||||
# fall back to tags
|
||||
pass
|
||||
|
||||
tags_url = f"https://api.github.com/repos/{owner}/{repo}/tags?per_page=1"
|
||||
payload = _github_get_json(tags_url)
|
||||
if not isinstance(payload, list) or not payload:
|
||||
raise UpdateCheckError("No releases or tags found on GitHub")
|
||||
|
||||
tag = str(payload[0].get("name") or "").strip()
|
||||
version = _normalize_tag_to_version(tag)
|
||||
if not version:
|
||||
raise UpdateCheckError("Latest GitHub tag is missing a version")
|
||||
|
||||
# Tags endpoint does not include a nice html_url for a release.
|
||||
return version, tag, f"https://github.com/{owner}/{repo}/releases/tag/{tag}"
|
||||
|
||||
|
||||
def check_for_update(owner: str, repo: str, current_version: Optional[str] = None) -> UpdateInfo:
|
||||
current = (current_version or get_current_version()).strip()
|
||||
latest_version, latest_tag, html_url = get_latest_github_release(owner, repo)
|
||||
|
||||
# Validate versions for comparison; if invalid, still return info.
|
||||
try:
|
||||
Version(current)
|
||||
Version(latest_version)
|
||||
except InvalidVersion:
|
||||
pass
|
||||
|
||||
return UpdateInfo(
|
||||
current_version=current,
|
||||
latest_version=latest_version,
|
||||
latest_tag=latest_tag,
|
||||
html_url=html_url,
|
||||
)
|
||||
Reference in New Issue
Block a user