Pivoted to in memory only storage

This commit is contained in:
2025-11-30 02:58:11 -05:00
parent 4da1739da4
commit 60d209c138
9 changed files with 519 additions and 190 deletions

View File

@@ -1,30 +1,28 @@
# services/torn_api.py
import aiohttp
import json
import asyncio
from pathlib import Path
from config import TORN_API_KEY
from .ffscouter import fetch_batch_stats
from .server_state import STATE
FRIENDLY_MEMBERS_FILE = Path("data/friendly_members.json")
FRIENDLY_STATUS_FILE = Path("data/friendly_status.json")
ENEMY_MEMBERS_FILE = Path("data/enemy_members.json")
ENEMY_STATUS_FILE = Path("data/enemy_status.json")
# -----------------------------
# Tasks
# -----------------------------
friendly_status_task = None
enemy_status_task = None
# Locks
# Locks for safe async updates
friendly_lock = asyncio.Lock()
enemy_lock = asyncio.Lock()
# -----------------------------
# Static population (once)
# Populate faction (memory only)
# -----------------------------
async def populate_faction(faction_id: int, members_file: Path, status_file: Path):
"""Fetch members + FFScouter estimates once and save static info + initial status."""
async def populate_faction(faction_id: int, kind: str):
"""
Fetch members + FFScouter estimates once and store in STATE.
kind: "friendly" or "enemy"
"""
url = f"https://api.torn.com/v2/faction/{faction_id}?selections=members&key={TORN_API_KEY}"
async with aiohttp.ClientSession() as session:
@@ -42,42 +40,37 @@ async def populate_faction(faction_id: int, members_file: Path, status_file: Pat
if not member_ids:
return False
# Fetch FFScouter data once
# Fetch FFScouter estimates
ff_data = await fetch_batch_stats(member_ids)
# Build static member list
members = []
status_data = {}
for m in members_list:
pid = m["id"]
est = ff_data.get(str(pid), {}).get("bs_estimate_human", "?")
member = {
"id": pid,
"name": m.get("name", "Unknown"),
"level": m.get("level", 0),
"estimate": est,
}
members.append(member)
# initial status
status_data[pid] = {"status": m.get("status", {}).get("state", "Unknown")}
received_ids = []
async with (friendly_lock if kind == "friendly" else enemy_lock):
for m in members_list:
pid = m["id"]
est = ff_data.get(str(pid), {}).get("bs_estimate_human", "?")
status = m.get("status", {}).get("state", "Unknown")
member_data = {
"id": pid,
"name": m.get("name", "Unknown"),
"level": m.get("level", 0),
"estimate": est,
"status": status
}
await STATE.upsert_member(member_data, kind)
received_ids.append(pid)
# Save members
members_file.parent.mkdir(exist_ok=True, parents=True)
with open(members_file, "w", encoding="utf-8") as f:
json.dump(members, f, indent=2)
# Save initial status
with open(status_file, "w", encoding="utf-8") as f:
json.dump(status_data, f, indent=2)
# Remove missing members from STATE
await STATE.remove_missing_members(received_ids, kind)
return True
# -----------------------------
# Status refresh loop
# -----------------------------
async def refresh_status_loop(faction_id: int, status_file: Path, lock: asyncio.Lock, interval: int):
"""Refresh only status from Torn API periodically."""
async def refresh_status_loop(faction_id: int, kind: str, lock: asyncio.Lock, interval: int):
"""
Periodically refresh member statuses in STATE.
"""
while True:
try:
url = f"https://api.torn.com/v2/faction/{faction_id}?selections=members&key={TORN_API_KEY}"
@@ -90,43 +83,39 @@ async def refresh_status_loop(faction_id: int, status_file: Path, lock: asyncio.
data = await resp.json()
members_list = data.get("members", [])
status_data = {m["id"]: {"status": m.get("status", {}).get("state", "Unknown")} for m in members_list}
# Save status safely
async with lock:
with open(status_file, "w", encoding="utf-8") as f:
json.dump(status_data, f, indent=2)
coll = STATE.friendly if kind == "friendly" else STATE.enemy
for m in members_list:
mid = m.get("id")
if mid in coll:
coll[mid].status = m.get("status", {}).get("state", "Unknown")
except Exception as e:
print(f"Error in status loop for {faction_id}: {e}")
print(f"Error in status loop for {kind} faction {faction_id}: {e}")
await asyncio.sleep(interval)
# -----------------------------
# Helper functions for endpoints
# Public API helpers
# -----------------------------
async def populate_friendly(faction_id: int):
return await populate_faction(faction_id, FRIENDLY_MEMBERS_FILE, FRIENDLY_STATUS_FILE)
return await populate_faction(faction_id, "friendly")
async def populate_enemy(faction_id: int):
return await populate_faction(faction_id, ENEMY_MEMBERS_FILE, ENEMY_STATUS_FILE)
return await populate_faction(faction_id, "enemy")
async def start_friendly_status_loop(faction_id: int, interval: int):
global friendly_status_task
if friendly_status_task and not friendly_status_task.done():
friendly_status_task.cancel()
friendly_status_task = asyncio.create_task(
refresh_status_loop(faction_id, FRIENDLY_STATUS_FILE, friendly_lock, interval)
refresh_status_loop(faction_id, "friendly", friendly_lock, interval)
)
async def start_enemy_status_loop(faction_id: int, interval: int):
global enemy_status_task
if enemy_status_task and not enemy_status_task.done():
enemy_status_task.cancel()
enemy_status_task = asyncio.create_task(
refresh_status_loop(faction_id, ENEMY_STATUS_FILE, enemy_lock, interval)
refresh_status_loop(faction_id, "enemy", enemy_lock, interval)
)