Files
PixelLauncher/launcher/stats.py
T
kosta2014p ec6f13ddcc PixelLauncher 2.1.0 — стабильная сборка
Офлайн-лаунчер Minecraft: аккаунты без Microsoft, установка версий и лоадеров
(Fabric, Quilt, Forge, NeoForge), каталог модпаков Modrinth, поиск и обновление
модов, разбор вылетов, системный трей, автозагрузка, плагины, прокси и зеркала.
2026-10-18 22:44:19 +07:00

133 lines
4.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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