"""Работа с манифестом версий Mojang (piston-meta).""" import json import os import time from . import http MANIFEST_URL = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json" MANIFEST_MAX_AGE = 3600 # свежий кэш не перекачиваем (час) _memory_cache = None _skip_ttl_once = False def _cache_dir(game_dir): d = os.path.join(game_dir, ".launcher") os.makedirs(d, exist_ok=True) return d def invalidate(): """Сбросить кэш: следующий get_manifest скачает манифест заново.""" global _memory_cache, _skip_ttl_once _memory_cache = None _skip_ttl_once = True def get_manifest(game_dir, net=True, max_age=MANIFEST_MAX_AGE): """Возвращает манифест версий. Свежий кэш на диске используется сразу, без обращения к сети. Сеть -> кэш на диске -> ошибка. """ global _memory_cache, _skip_ttl_once if _memory_cache is not None: return _memory_cache cache = os.path.join(_cache_dir(game_dir), "version_manifest.json") force = _skip_ttl_once _skip_ttl_once = False fresh = False if os.path.isfile(cache): try: fresh = (time.time() - os.path.getmtime(cache)) < max_age except OSError: fresh = False if net and (force or not fresh): try: data = json.loads(http.get_bytes(MANIFEST_URL, timeout=20)) with open(cache, "w", encoding="utf-8") as f: json.dump(data, f) _memory_cache = data return data except Exception: pass if os.path.isfile(cache): with open(cache, encoding="utf-8") as f: _memory_cache = json.load(f) return _memory_cache raise RuntimeError("Нет доступа к сети и нет кэша манифеста версий") def version_url(manifest, vid): for v in manifest.get("versions", []): if v.get("id") == vid: return v.get("url") return None def local_versions(game_dir): """Идентификаторы версий, установленных локально (Fabric, Quilt и т.п.).""" root = os.path.join(game_dir, "versions") out = [] try: for name in sorted(os.listdir(root)): if os.path.isfile(os.path.join(root, name, name + ".json")): out.append(name) except OSError: pass return out def fetch_version_json(game_dir, vid, net=True, max_age=21600): """JSON конкретной версии: свежий кэш -> сеть -> versions/{id}/{id}.json.""" path = os.path.join(game_dir, "versions", vid, vid + ".json") fresh = False if os.path.isfile(path): try: fresh = (time.time() - os.path.getmtime(path)) < max_age except OSError: fresh = False if net and not fresh: try: manifest = get_manifest(game_dir, net=True) url = version_url(manifest, vid) if not url: raise RuntimeError(f"Версия {vid} не найдена в манифесте") raw = http.get_bytes(url, timeout=20) os.makedirs(os.path.dirname(path), exist_ok=True) with open(path, "wb") as f: f.write(raw) return json.loads(raw) except Exception: pass if os.path.isfile(path): with open(path, encoding="utf-8") as f: return json.load(f) raise RuntimeError(f"Не удалось получить JSON версии {vid}")