PixelLauncher 2.1.0 — стабильная сборка
Офлайн-лаунчер Minecraft: аккаунты без Microsoft, установка версий и лоадеров (Fabric, Quilt, Forge, NeoForge), каталог модпаков Modrinth, поиск и обновление модов, разбор вылетов, системный трей, автозагрузка, плагины, прокси и зеркала.
This commit is contained in:
commit
ec6f13ddcc
33 files changed
+10445
No files matched your search
@@ -0,0 +1,132 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""История запусков и статистика игры.
|
||||
|
||||
Всё хранится в .launcher/stats.json рядом с кэшем Java:
|
||||
plays — {версия: {count, seconds, last, crashes}}
|
||||
launches — последние запуски (кап 300): {ts, ver, ok, code, seconds}
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
|
||||
_FILE = None # путь к stats.json (задаётся set_path)
|
||||
_LOCK = threading.Lock()
|
||||
_MAX_LAUNCHES = 300
|
||||
|
||||
|
||||
def set_path(launcher_dir):
|
||||
"""Каталог .launcher — тут лежат stats.json и java_cache.json."""
|
||||
global _FILE
|
||||
_FILE = os.path.join(str(launcher_dir), "stats.json")
|
||||
|
||||
|
||||
def _load():
|
||||
try:
|
||||
with open(_FILE, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
if not isinstance(data, dict):
|
||||
raise ValueError
|
||||
return data
|
||||
except (OSError, ValueError):
|
||||
return {"plays": {}, "launches": []}
|
||||
|
||||
|
||||
def _save(data):
|
||||
tmp = _FILE + ".tmp"
|
||||
try:
|
||||
with open(tmp, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, ensure_ascii=False)
|
||||
os.replace(tmp, _FILE)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def record_start(version):
|
||||
"""Вызывается при запуске игры: +1 к счётчику запусков версии."""
|
||||
if not _FILE:
|
||||
return
|
||||
with _LOCK:
|
||||
data = _load()
|
||||
plays = data.setdefault("plays", {})
|
||||
rec = plays.setdefault(version, {"count": 0, "seconds": 0,
|
||||
"last": 0.0, "crashes": 0})
|
||||
rec["count"] += 1
|
||||
rec["last"] = time.time()
|
||||
_save(data)
|
||||
|
||||
|
||||
def record_finish(version, seconds, ok, code):
|
||||
"""Вызывается при завершении игры: длительность, код, вылет."""
|
||||
if not _FILE:
|
||||
return
|
||||
with _LOCK:
|
||||
data = _load()
|
||||
plays = data.setdefault("plays", {})
|
||||
rec = plays.setdefault(version, {"count": 0, "seconds": 0,
|
||||
"last": 0.0, "crashes": 0})
|
||||
rec["seconds"] = max(0, int(seconds))
|
||||
if not ok:
|
||||
rec["crashes"] = rec.get("crashes", 0) + 1
|
||||
launches = data.setdefault("launches", [])
|
||||
launches.append({"ts": time.time(), "ver": version, "ok": bool(ok),
|
||||
"code": code, "seconds": max(0, int(seconds))})
|
||||
del launches[:-_MAX_LAUNCHES]
|
||||
_save(data)
|
||||
|
||||
|
||||
def totals():
|
||||
"""Агрегаты для окна статистики."""
|
||||
if not _FILE:
|
||||
return {"total_seconds": 0, "total_launches": 0,
|
||||
"per_version": [], "recent": []}
|
||||
with _LOCK:
|
||||
data = _load()
|
||||
plays = data.get("plays") or {}
|
||||
per = []
|
||||
total_s = 0
|
||||
for ver, rec in plays.items():
|
||||
sec = int(rec.get("seconds") or 0)
|
||||
total_s += sec
|
||||
per.append({"ver": ver, "count": int(rec.get("count") or 0),
|
||||
"seconds": sec, "last": float(rec.get("last") or 0),
|
||||
"crashes": int(rec.get("crashes") or 0)})
|
||||
per.sort(key=lambda r: r["last"], reverse=True)
|
||||
recent = list(data.get("launches") or [])[-20:]
|
||||
recent.reverse()
|
||||
return {"total_seconds": total_s,
|
||||
"total_launches": sum(r["count"] for r in per),
|
||||
"per_version": per, "recent": recent}
|
||||
|
||||
|
||||
def fmt_duration(seconds):
|
||||
"""1800 → «30 мин»; 30 → «30 с»; 3700 → «1 ч 1 мин»."""
|
||||
seconds = int(seconds or 0)
|
||||
if seconds < 60:
|
||||
return f"{seconds} с"
|
||||
if seconds < 3600:
|
||||
return f"{seconds // 60} мин"
|
||||
h, m = seconds // 3600, (seconds % 3600) // 60
|
||||
return f"{h} ч {m} мин" if m else f"{h} ч"
|
||||
|
||||
|
||||
def fmt_time(ts):
|
||||
"""Unix-время → «дд.мм.гггг чч:мм»."""
|
||||
if not ts:
|
||||
return "—"
|
||||
try:
|
||||
return time.strftime("%d.%m.%Y %H:%M", time.localtime(ts))
|
||||
except (ValueError, OSError):
|
||||
return "—"
|
||||
|
||||
|
||||
def clear():
|
||||
"""Полная очистка истории и статистики."""
|
||||
if not _FILE:
|
||||
return
|
||||
with _LOCK:
|
||||
try:
|
||||
os.remove(_FILE)
|
||||
except OSError:
|
||||
pass
|
||||
Reference in new issue
Block a user