""" RustDesk 可选版本列表。 优先从 GitHub 官方仓库 rustdesk/rustdesk 的 tags 动态获取(跟随官方发版自动更新), 进程内缓存 6 小时;网络失败时回退内置版本列表,且失败结果短缓存 10 分钟, 避免每次打开页面都等待 GitHub 超时。所有请求经由 ghnet,自动使用代理配置。 """ import re import time from . import ghnet TAGS_URL = 'https://api.github.com/repos/rustdesk/rustdesk/tags?per_page=100' # 离线兜底(截至本版本发布时的近期正式版) FALLBACK_VERSIONS = [ '1.4.9', '1.4.8', '1.4.7', '1.4.6', '1.4.5', '1.4.4', '1.4.3', '1.4.2', '1.4.1', '1.4.0', ] # 形如 1.4.9 / 1.4.10 / 1.5.0-rc.1 的正式 tag TAG_RE = re.compile(r'^\d+\.\d+\.\d+(?:[.-][0-9A-Za-z.]+)?$') # 表单/API 对用户提交版本号的宽松校验,兼容动态列表刷新窗口期 VERSION_RE = re.compile(r'^(?:master|main|\d+\.\d+\.\d+(?:[.-][0-9A-Za-z.]+)?)$') MAX_VERSIONS = 20 CACHE_TTL = 6 * 3600 # 成功结果缓存 6 小时 FALLBACK_TTL = 10 * 60 # 失败回退缓存 10 分钟 _memo = { 'ts': 0.0, 'ttl': CACHE_TTL, 'versions': FALLBACK_VERSIONS, 'source': 'builtin', } def _sort_versions(versions): """按版本号数字段降序排列,预发布标识排在同正式版之后。""" def key(v): main, _, suffix = v.partition('-') parts = [int(x) if x.isdigit() else 0 for x in main.split('.')] parts += [0] * (3 - len(parts)) # 无后缀(正式版)排在 rc/beta 之前 return (parts, 1 if not suffix else 0, suffix) return sorted(versions, key=key, reverse=True) def _fetch_from_github(): resp = ghnet.get(TAGS_URL, timeout=8) resp.raise_for_status() tags = [item['name'].lstrip('v') for item in resp.json() if item.get('name')] versions = _sort_versions({t for t in tags if TAG_RE.match(t)}) return versions[:MAX_VERSIONS] def get_versions(force=False): """返回 (版本号列表, 来源 source, 缓存时间戳);source: github / builtin。""" now = time.time() if not force and now - _memo['ts'] < _memo['ttl']: return _memo['versions'], _memo['source'], _memo['ts'] try: versions = _fetch_from_github() if versions: _memo.update(ts=now, ttl=CACHE_TTL, versions=versions, source='github') else: _memo.update(ts=now, ttl=FALLBACK_TTL, versions=FALLBACK_VERSIONS, source='builtin') except Exception: _memo.update(ts=now, ttl=FALLBACK_TTL, versions=FALLBACK_VERSIONS, source='builtin') return _memo['versions'], _memo['source'], _memo['ts'] def version_choices(): """供 ChoiceField 使用的 (value, label) 列表。""" versions, source, _ = get_versions() label_suffix = '' if source == 'github' else '(内置列表,获取官方列表失败)' choices = [('master', '开发版(nightly 每夜构建)')] choices += [(v, v) for v in versions] return choices def is_valid_version(value): """API / 表单提交的版本号合法性校验。""" return bool(value) and bool(VERSION_RE.match(str(value)))