123 lines
4.3 KiB
Python
123 lines
4.3 KiB
Python
from __future__ import annotations
|
|
import json, os, uuid
|
|
from typing import List, Dict
|
|
from flask import current_app
|
|
from .models import Actor, Preset, PresetGroup, CONDITION_ICON_FILES, CURATED_ICON_SLUGS
|
|
|
|
PLACEHOLDER_BG = '#334155'
|
|
PLACEHOLDER_FG = '#e2e8f0'
|
|
GAME_ICONS_BASE = 'https://raw.githubusercontent.com/game-icons/icons/master'
|
|
|
|
def paths():
|
|
return dict(
|
|
STATE=current_app.config["STATE_PATH"],
|
|
PRESETS=current_app.config["PRESETS_PATH"],
|
|
GROUPS=current_app.config["PRESET_GROUPS_PATH"],
|
|
AVATARS=current_app.config["AVATAR_DIR"],
|
|
ICONS=current_app.config["ICON_DIR"],
|
|
DATA=current_app.config["DATA_DIR"],
|
|
)
|
|
|
|
def save_state(state) -> None:
|
|
p = paths()
|
|
tmp = {
|
|
'actors': [a.__dict__ for a in state.actors],
|
|
'turn_idx': state.turn_idx,
|
|
'round': state.round,
|
|
'visible': state.visible,
|
|
'dead_mode': state.dead_mode,
|
|
}
|
|
with open(p["STATE"], 'w', encoding='utf-8') as f:
|
|
json.dump(tmp, f, ensure_ascii=False, indent=2)
|
|
|
|
def load_state(state) -> None:
|
|
p = paths()
|
|
if not os.path.exists(p["STATE"]):
|
|
return
|
|
try:
|
|
with open(p["STATE"], 'r', encoding='utf-8') as f:
|
|
data = json.load(f)
|
|
state.actors = [Actor(**a) for a in data.get('actors', [])]
|
|
state.turn_idx = int(data.get('turn_idx', 0))
|
|
state.round = int(data.get('round', 1))
|
|
state.visible = bool(data.get('visible', True))
|
|
state.dead_mode = str(data.get('dead_mode', 'normal'))
|
|
state.normalize()
|
|
state.touch()
|
|
except Exception:
|
|
pass
|
|
|
|
def save_presets(items: List[Preset]) -> None:
|
|
p = paths()
|
|
with open(p["PRESETS"], 'w', encoding='utf-8') as f:
|
|
json.dump([x.__dict__ for x in items], f, ensure_ascii=False, indent=2)
|
|
|
|
def load_presets() -> List[Preset]:
|
|
p = paths()
|
|
if not os.path.exists(p["PRESETS"]):
|
|
return []
|
|
try:
|
|
with open(p["PRESETS"], 'r', encoding='utf-8') as f:
|
|
arr = json.load(f)
|
|
return [Preset(**x) for x in arr]
|
|
except Exception:
|
|
return []
|
|
|
|
def save_groups(items: List[PresetGroup]) -> None:
|
|
p = paths()
|
|
with open(p["GROUPS"], 'w', encoding='utf-8') as f:
|
|
json.dump([x.__dict__ for x in items], f, ensure_ascii=False, indent=2)
|
|
|
|
def load_groups() -> List[PresetGroup]:
|
|
p = paths()
|
|
if not os.path.exists(p["GROUPS"]):
|
|
return []
|
|
try:
|
|
with open(p["GROUPS"], 'r', encoding='utf-8') as f:
|
|
arr = json.load(f)
|
|
return [PresetGroup(**x) for x in arr]
|
|
except Exception:
|
|
return []
|
|
|
|
def ensure_default_icons() -> None:
|
|
p = paths()
|
|
os.makedirs(p["ICONS"], exist_ok=True)
|
|
for c, fn in CONDITION_ICON_FILES.items():
|
|
dest = os.path.join(p["ICONS"], fn)
|
|
if os.path.exists(dest):
|
|
continue
|
|
letter = (c[:1] or '?').upper()
|
|
svg = f"""<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'>
|
|
<rect rx='6' width='32' height='32' fill='{PLACEHOLDER_BG}' />
|
|
<text x='16' y='21' font-family='sans-serif' font-size='16' text-anchor='middle' fill='{PLACEHOLDER_FG}'>{letter}</text>
|
|
</svg>"""
|
|
try:
|
|
with open(dest, 'w', encoding='utf-8') as f:
|
|
f.write(svg)
|
|
except Exception:
|
|
pass
|
|
|
|
def seed_curated_icons(overwrite: bool = True) -> list[dict]:
|
|
ensure_default_icons()
|
|
out = []
|
|
try:
|
|
from urllib.request import urlopen
|
|
from urllib.error import URLError, HTTPError
|
|
p = paths()
|
|
for cond, (author, slug) in CURATED_ICON_SLUGS.items():
|
|
dest = os.path.join(p["ICONS"], f'{cond}.svg')
|
|
url = f"https://raw.githubusercontent.com/game-icons/icons/master/{author}/{slug}.svg"
|
|
try:
|
|
with urlopen(url, timeout=10) as r:
|
|
svg = r.read()
|
|
if (not os.path.exists(dest)) or overwrite:
|
|
with open(dest, 'wb') as f:
|
|
f.write(svg)
|
|
out.append({'condition': cond, 'status': 'downloaded'})
|
|
else:
|
|
out.append({'condition': cond, 'status': 'exists'})
|
|
except (URLError, HTTPError) as e:
|
|
out.append({'condition': cond, 'status': f'fallback ({e.__class__.__name__})'})
|
|
finally:
|
|
return out
|