Merge branch 'main' into fix/ui-appearance-polish
Some checks failed
Build and Test / build (pull_request) Failing after 17m32s
Some checks failed
Build and Test / build (pull_request) Failing after 17m32s
This commit is contained in:
@@ -1,11 +1,20 @@
|
||||
# Kindred Create core module
|
||||
# Handles auto-loading of ztools and Silo addons
|
||||
|
||||
# Generate version.py from template with Kindred Create version
|
||||
configure_file(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/version.py.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/version.py
|
||||
@ONLY
|
||||
)
|
||||
|
||||
# Install Python init files
|
||||
install(
|
||||
FILES
|
||||
Init.py
|
||||
InitGui.py
|
||||
update_checker.py
|
||||
${CMAKE_CURRENT_BINARY_DIR}/version.py
|
||||
DESTINATION
|
||||
Mod/Create
|
||||
)
|
||||
|
||||
@@ -148,6 +148,16 @@ def _setup_silo_activity_panel():
|
||||
FreeCAD.Console.PrintLog(f"Create: Silo activity panel skipped: {e}\n")
|
||||
|
||||
|
||||
def _check_for_updates():
|
||||
"""Check for application updates in the background."""
|
||||
try:
|
||||
from update_checker import _run_update_check
|
||||
|
||||
_run_update_check()
|
||||
except Exception as e:
|
||||
FreeCAD.Console.PrintLog(f"Create: Update check skipped: {e}\n")
|
||||
|
||||
|
||||
# Defer enhancements until the GUI event loop is running
|
||||
try:
|
||||
from PySide.QtCore import QTimer
|
||||
@@ -156,5 +166,6 @@ try:
|
||||
QTimer.singleShot(2000, _setup_silo_auth_panel)
|
||||
QTimer.singleShot(3000, _check_silo_first_start)
|
||||
QTimer.singleShot(4000, _setup_silo_activity_panel)
|
||||
QTimer.singleShot(10000, _check_for_updates)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
165
src/Mod/Create/update_checker.py
Normal file
165
src/Mod/Create/update_checker.py
Normal file
@@ -0,0 +1,165 @@
|
||||
"""Kindred Create update checker.
|
||||
|
||||
Queries the Gitea releases API to determine if a newer version is
|
||||
available. Designed to run in the background on startup without
|
||||
blocking the UI.
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import urllib.request
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import FreeCAD
|
||||
|
||||
_RELEASES_URL = "https://git.kindred-systems.com/api/v1/repos/kindred/create/releases"
|
||||
_PREF_PATH = "User parameter:BaseApp/Preferences/Mod/KindredCreate/Update"
|
||||
_TIMEOUT = 5
|
||||
|
||||
|
||||
def _parse_version(tag):
|
||||
"""Parse a version tag like 'v0.1.3' into a comparable tuple.
|
||||
|
||||
Returns None if the tag doesn't match the expected pattern.
|
||||
"""
|
||||
m = re.match(r"^v?(\d+)\.(\d+)\.(\d+)$", tag)
|
||||
if not m:
|
||||
return None
|
||||
return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
|
||||
|
||||
|
||||
def check_for_update(current_version):
|
||||
"""Check if a newer release is available on Gitea.
|
||||
|
||||
Args:
|
||||
current_version: Version string like "0.1.3".
|
||||
|
||||
Returns:
|
||||
Dict with update info if a newer version exists, None otherwise.
|
||||
Dict keys: version, tag, release_url, assets, body.
|
||||
"""
|
||||
current = _parse_version(current_version)
|
||||
if current is None:
|
||||
return None
|
||||
|
||||
req = urllib.request.Request(
|
||||
f"{_RELEASES_URL}?limit=10",
|
||||
headers={"Accept": "application/json"},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=_TIMEOUT) as resp:
|
||||
releases = json.loads(resp.read())
|
||||
|
||||
best = None
|
||||
best_version = current
|
||||
|
||||
for release in releases:
|
||||
if release.get("draft"):
|
||||
continue
|
||||
if release.get("prerelease"):
|
||||
continue
|
||||
|
||||
tag = release.get("tag_name", "")
|
||||
# Skip the rolling 'latest' tag
|
||||
if tag == "latest":
|
||||
continue
|
||||
|
||||
ver = _parse_version(tag)
|
||||
if ver is None:
|
||||
continue
|
||||
|
||||
if ver > best_version:
|
||||
best_version = ver
|
||||
best = release
|
||||
|
||||
if best is None:
|
||||
return None
|
||||
|
||||
assets = []
|
||||
for asset in best.get("assets", []):
|
||||
assets.append(
|
||||
{
|
||||
"name": asset.get("name", ""),
|
||||
"url": asset.get("browser_download_url", ""),
|
||||
"size": asset.get("size", 0),
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"version": ".".join(str(x) for x in best_version),
|
||||
"tag": best["tag_name"],
|
||||
"release_url": best.get("html_url", ""),
|
||||
"assets": assets,
|
||||
"body": best.get("body", ""),
|
||||
}
|
||||
|
||||
|
||||
def _should_check(param):
|
||||
"""Determine whether an update check should run now.
|
||||
|
||||
Args:
|
||||
param: FreeCAD parameter group for update preferences.
|
||||
|
||||
Returns:
|
||||
True if a check should be performed.
|
||||
"""
|
||||
if not param.GetBool("CheckEnabled", True):
|
||||
return False
|
||||
|
||||
last_check = param.GetString("LastCheckTimestamp", "")
|
||||
if not last_check:
|
||||
return True
|
||||
|
||||
interval_days = param.GetInt("CheckIntervalDays", 1)
|
||||
if interval_days <= 0:
|
||||
return True
|
||||
|
||||
try:
|
||||
last_dt = datetime.fromisoformat(last_check)
|
||||
now = datetime.now(timezone.utc)
|
||||
elapsed = (now - last_dt).total_seconds()
|
||||
return elapsed >= interval_days * 86400
|
||||
except (ValueError, TypeError):
|
||||
return True
|
||||
|
||||
|
||||
def _run_update_check():
|
||||
"""Entry point called from the deferred startup timer."""
|
||||
param = FreeCAD.ParamGet(_PREF_PATH)
|
||||
|
||||
if not _should_check(param):
|
||||
return
|
||||
|
||||
try:
|
||||
from version import VERSION
|
||||
except ImportError:
|
||||
FreeCAD.Console.PrintLog(
|
||||
"Create: update check skipped — version module not available\n"
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
result = check_for_update(VERSION)
|
||||
except Exception as e:
|
||||
FreeCAD.Console.PrintLog(f"Create: update check failed: {e}\n")
|
||||
return
|
||||
|
||||
# Record that we checked
|
||||
param.SetString(
|
||||
"LastCheckTimestamp",
|
||||
datetime.now(timezone.utc).isoformat(),
|
||||
)
|
||||
|
||||
if result is None:
|
||||
FreeCAD.Console.PrintLog("Create: application is up to date\n")
|
||||
return
|
||||
|
||||
skipped = param.GetString("SkippedVersion", "")
|
||||
if result["version"] == skipped:
|
||||
FreeCAD.Console.PrintLog(
|
||||
f"Create: update {result['version']} available but skipped by user\n"
|
||||
)
|
||||
return
|
||||
|
||||
FreeCAD.Console.PrintMessage(
|
||||
f"Kindred Create {result['version']} is available (current: {VERSION})\n"
|
||||
)
|
||||
1
src/Mod/Create/version.py.in
Normal file
1
src/Mod/Create/version.py.in
Normal file
@@ -0,0 +1 @@
|
||||
VERSION = "@KINDRED_CREATE_VERSION@"
|
||||
Reference in New Issue
Block a user