Source code for scfile.utils.updates
"""Application updates checking."""
import json
import urllib.request
from typing import Any, NamedTuple
from scfile import __repository__ as REPO
from scfile.enums import UpdateStatus as Status
from . import files
from .versions import Version
TIMEOUT = 5
[docs]
class UpdateCheck(NamedTuple):
status: Status
message: str
url: str
def _fetch(url: str) -> dict[str, Any] | None:
headers = {"User-Agent": f"{REPO}", "Cache-Control": "no-cache"}
try:
req = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(req, timeout=TIMEOUT) as response:
return json.loads(response.read().decode())
except Exception:
return None
[docs]
def current() -> str | None:
"""Read local commit SHA from bundled file."""
try:
commit = files.resource("commit")
if commit.exists():
return commit.read_text().strip()
except Exception:
pass
return None
def _check_dev(v: Version) -> UpdateCheck:
sha = current()
if not sha:
url = f"https://github.com/{REPO}/releases/tag/{v.tag}"
return UpdateCheck(Status.ERROR, "local commit sha not found", url)
data = _fetch(f"https://api.github.com/repos/{REPO}/commits/{v.tag}")
if not data:
return UpdateCheck(Status.ERROR, "network error", "")
remote_sha = data.get("sha")
if remote_sha != sha:
return UpdateCheck(Status.AVAILABLE, "", f"https://github.com/{REPO}/releases/tag/{v.tag}")
return UpdateCheck(Status.UPTODATE, "", "")
def _check_release(v: Version) -> UpdateCheck:
data = _fetch(f"https://api.github.com/repos/{REPO}/releases/latest")
if data is None:
return UpdateCheck(Status.ERROR, "network error", "")
tag = data.get("tag_name", "")
remote_v = Version.parse(tag)
if not remote_v:
return UpdateCheck(Status.ERROR, f"invalid remote version format '{tag}'", "")
if remote_v and remote_v > v:
return UpdateCheck(Status.AVAILABLE, "", f"https://github.com/{REPO}/releases/tag/{tag}")
return UpdateCheck(Status.UPTODATE, "", "")
[docs]
def check(semver: str) -> UpdateCheck:
"""Check GitHub for a newer version."""
v = Version.parse(semver)
if not v:
return UpdateCheck(Status.ERROR, f"invalid version format '{semver}'", "")
if v.is_dev:
return _check_dev(v)
return _check_release(v)