test14
This commit is contained in:
136
get_data_new.py
136
get_data_new.py
@@ -410,33 +410,38 @@ def run_live_loop(
|
|||||||
|
|
||||||
|
|
||||||
def poll_game_live(
|
def poll_game_live(
|
||||||
session, league: str, season: str, game_id: int, lang: str, game_meta: dict
|
session,
|
||||||
|
league: str,
|
||||||
|
season: str,
|
||||||
|
game_id: int,
|
||||||
|
lang: str,
|
||||||
|
game_meta: dict,
|
||||||
|
stop_event: threading.Event,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Онлайн-цикл:
|
Онлайн-цикл:
|
||||||
- "game" и "pregame-fullstats" раз в 600 сек
|
- "game" раз в 600 сек (pregame-fullstats можно вернуть позже)
|
||||||
- "live-status", "box-score", "play-by-play" раз в 1 сек
|
- "live-status", "box-score", "play-by-play" раз в 1 сек
|
||||||
|
|
||||||
Всё, что надо сейчас дернуть, дергаем параллельно.
|
Цикл выходит, когда матч перестаёт быть live ИЛИ когда сработал stop_event.
|
||||||
Цикл выходит, когда матч перестаёт быть live.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
slow_endpoints = [
|
slow_endpoints = ["game"]
|
||||||
"game",
|
|
||||||
] # "pregame-fullstats"]
|
|
||||||
fast_endpoints = ["live-status", "box-score", "play-by-play"]
|
fast_endpoints = ["live-status", "box-score", "play-by-play"]
|
||||||
|
|
||||||
last_call = {}
|
last_call = {ep: 0 for ep in slow_endpoints + fast_endpoints}
|
||||||
now = time.time()
|
|
||||||
for name in slow_endpoints + fast_endpoints:
|
|
||||||
last_call[name] = 0 # форсим первый вызов сразу
|
|
||||||
|
|
||||||
# пул потоков: 5 нам хватит (у нас максимум 5 ручек одновременно)
|
# пул потоков живет весь матч
|
||||||
with ThreadPoolExecutor(max_workers=5) as executor:
|
with ThreadPoolExecutor(max_workers=5) as executor:
|
||||||
while True:
|
while True:
|
||||||
|
# внешняя принудительная остановка
|
||||||
|
if stop_event.is_set():
|
||||||
|
logger.info(f"[LIVE_LOOP] stop_event set -> break live poll for game {game_id}")
|
||||||
|
break
|
||||||
|
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
|
||||||
# собрать, какие ручки надо дёрнуть прямо сейчас
|
# какие ручки надо дёрнуть прямо сейчас
|
||||||
to_run = []
|
to_run = []
|
||||||
for ep in fast_endpoints + slow_endpoints:
|
for ep in fast_endpoints + slow_endpoints:
|
||||||
interval = get_interval_by_name(ep)
|
interval = get_interval_by_name(ep)
|
||||||
@@ -458,18 +463,19 @@ def poll_game_live(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# будем смотреть на ответы, особенно live-status
|
|
||||||
game_finished = False
|
game_finished = False
|
||||||
|
|
||||||
for fut in as_completed(futures):
|
for fut in as_completed(futures):
|
||||||
try:
|
try:
|
||||||
ep_name, data = fut.result()
|
ep_name, data = fut.result()
|
||||||
last_call[ep_name] = now # помечаем как обновлённый
|
last_call[ep_name] = now
|
||||||
|
|
||||||
if ep_name == "live-status":
|
if ep_name == "live-status":
|
||||||
# проверяем статус, не закончилась ли игра
|
|
||||||
if isinstance(data, dict):
|
if isinstance(data, dict):
|
||||||
st = (
|
st = (
|
||||||
data.get("status") or data.get("gameStatus") or ""
|
data.get("status")
|
||||||
|
or data.get("gameStatus")
|
||||||
|
or ""
|
||||||
).lower()
|
).lower()
|
||||||
if st in ("resultconfirmed", "finished", "final"):
|
if st in ("resultconfirmed", "finished", "final"):
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -480,7 +486,7 @@ def poll_game_live(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"poll endpoint error: {e}")
|
logger.exception(f"poll endpoint error: {e}")
|
||||||
|
|
||||||
# вторая страховка (инфо из календаря)
|
# страховка по календарю (game_meta мог устареть, но лучше чем ничего)
|
||||||
if not is_game_live(game_meta):
|
if not is_game_live(game_meta):
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Game {game_id} no longer live by calendar meta -> stop loop"
|
f"Game {game_id} no longer live by calendar meta -> stop loop"
|
||||||
@@ -490,7 +496,6 @@ def poll_game_live(
|
|||||||
if game_finished:
|
if game_finished:
|
||||||
break
|
break
|
||||||
|
|
||||||
# чуть притормозим, чтобы не жарить CPU
|
|
||||||
time.sleep(0.2)
|
time.sleep(0.2)
|
||||||
logger.debug("live poll tick ok")
|
logger.debug("live poll tick ok")
|
||||||
|
|
||||||
@@ -518,7 +523,7 @@ def get_data_API(session, league: str, team: str, lang: str):
|
|||||||
|
|
||||||
today_game, last_played = get_game_id(json_calendar, team)
|
today_game, last_played = get_game_id(json_calendar, team)
|
||||||
|
|
||||||
# если есть завершённая последняя игра — просто сохраним её статические данные
|
# если есть завершённая последняя игра — просто сохраним статический срез и выходим
|
||||||
if last_played and not today_game:
|
if last_played and not today_game:
|
||||||
game_id = last_played["game"]["id"]
|
game_id = last_played["game"]["id"]
|
||||||
logger.info(f"Последний завершённый матч id={game_id}")
|
logger.info(f"Последний завершённый матч id={game_id}")
|
||||||
@@ -530,36 +535,65 @@ def get_data_API(session, league: str, team: str, lang: str):
|
|||||||
game_id = today_game["game"]["id"]
|
game_id = today_game["game"]["id"]
|
||||||
logger.info(f"Онлайн матч id={game_id}")
|
logger.info(f"Онлайн матч id={game_id}")
|
||||||
|
|
||||||
# базовые данные прямо сейчас (до запуска фонового потока)
|
# базовые данные прямо сейчас
|
||||||
fetch_api_data(session, "game", host=HOST, game_id=game_id, lang=lang)
|
fetch_api_data(session, "game", host=HOST, game_id=game_id, lang=lang)
|
||||||
# fetch_api_data(
|
|
||||||
# session,
|
|
||||||
# "pregame-fullstats",
|
|
||||||
# host=HOST,
|
|
||||||
# league=league,
|
|
||||||
# season=season,
|
|
||||||
# game_id=game_id,
|
|
||||||
# lang=lang,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# если матч реально идёт -> запускаем отдельный поток live-опроса
|
# если матч реально идёт -> запускаем live-петлю и рендер
|
||||||
if is_game_live(today_game["game"]):
|
if is_game_live(today_game["game"]):
|
||||||
t = threading.Thread(
|
# отдельная сессия для live-пула (как раньше)
|
||||||
target=run_live_loop,
|
live_session = create_session()
|
||||||
args=(session, league, season, game_id, lang, today_game["game"]),
|
|
||||||
daemon=False, # <-- ключевое изменение
|
# единый stop_event для всего матча
|
||||||
)
|
stop_event = threading.Event()
|
||||||
t.start()
|
|
||||||
logger.info("live thread spawned, waiting for it to finish...")
|
# поток рендера
|
||||||
|
render_thread = threading.Thread(
|
||||||
|
target=render_loop,
|
||||||
|
args=(stop_event, "ui_state"), # имя файла можешь менять
|
||||||
|
daemon=False,
|
||||||
|
)
|
||||||
|
render_thread.start()
|
||||||
|
logger.info("[MAIN] render thread spawned")
|
||||||
|
|
||||||
|
# поток live-пулинга API
|
||||||
|
def live_worker():
|
||||||
|
try:
|
||||||
|
poll_game_live(
|
||||||
|
session=live_session,
|
||||||
|
league=league,
|
||||||
|
season=season,
|
||||||
|
game_id=game_id,
|
||||||
|
lang=lang,
|
||||||
|
game_meta=today_game["game"],
|
||||||
|
stop_event=stop_event,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(f"[LIVE_THREAD] crash in live loop: {e}")
|
||||||
|
|
||||||
|
live_thread = threading.Thread(
|
||||||
|
target=live_worker,
|
||||||
|
daemon=False,
|
||||||
|
)
|
||||||
|
live_thread.start()
|
||||||
|
logger.info("[MAIN] live thread spawned")
|
||||||
|
|
||||||
|
# дожидаемся окончания live_thread (то есть завершения матча или ошибки)
|
||||||
|
live_thread.join()
|
||||||
|
logger.info("[MAIN] live thread finished")
|
||||||
|
|
||||||
|
# говорим рендеру остановиться
|
||||||
|
stop_event.set()
|
||||||
|
|
||||||
|
# ждём корректного завершения рендера
|
||||||
|
render_thread.join()
|
||||||
|
logger.info("[MAIN] render thread finished")
|
||||||
|
|
||||||
# блокируем main до конца матча
|
|
||||||
t.join()
|
|
||||||
logger.info("live thread finished")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info("Для этой команды игр сегодня нет и нет завершённой последней игры.")
|
logger.info("Для этой команды игр сегодня нет и нет завершённой последней игры.")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def read_local_json(name: str, in_dir: str = "static"):
|
def read_local_json(name: str, in_dir: str = "static"):
|
||||||
"""
|
"""
|
||||||
Безопасно читает static/<name>.json.
|
Безопасно читает static/<name>.json.
|
||||||
@@ -1544,7 +1578,7 @@ def Team_Both_Stat(merged: dict, *, out_dir: str = "static") -> None:
|
|||||||
def render_loop(stop_event: threading.Event, out_name: str = "game"):
|
def render_loop(stop_event: threading.Event, out_name: str = "game"):
|
||||||
"""
|
"""
|
||||||
Крутится в отдельном потоке.
|
Крутится в отдельном потоке.
|
||||||
Постоянно читает сырые api_*.json, собирает финальный state
|
Читает api_*.json, собирает финальный state
|
||||||
и сохраняет в static/<out_name>.json.
|
и сохраняет в static/<out_name>.json.
|
||||||
Работает, пока stop_event не установлен.
|
Работает, пока stop_event не установлен.
|
||||||
"""
|
"""
|
||||||
@@ -1558,12 +1592,10 @@ def render_loop(stop_event: threading.Event, out_name: str = "game"):
|
|||||||
Json_Team_Generation(state, who="team2")
|
Json_Team_Generation(state, who="team2")
|
||||||
atomic_write_json([state["result"]["live_status"]], "live_status")
|
atomic_write_json([state["result"]["live_status"]], "live_status")
|
||||||
atomic_write_json(state["result"], out_name)
|
atomic_write_json(state["result"], out_name)
|
||||||
|
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
logger.exception(f"[RENDER_THREAD] error while building render state: {ex}")
|
logger.exception(f"[RENDER_THREAD] error while building render state: {ex}")
|
||||||
|
|
||||||
# частота обновления отрисовки.
|
|
||||||
# 0.2с достаточно быстро для ТВ-графики и не жарит CPU.
|
|
||||||
time.sleep(0.2)
|
time.sleep(0.2)
|
||||||
|
|
||||||
logger.info("[RENDER_THREAD] stop render loop")
|
logger.info("[RENDER_THREAD] stop render loop")
|
||||||
@@ -1578,7 +1610,19 @@ def main():
|
|||||||
|
|
||||||
session = create_session()
|
session = create_session()
|
||||||
|
|
||||||
get_data_API(session, args.league, args.team, args.lang)
|
# единый флаг остановки на ВСЮ программу
|
||||||
|
stop_event = threading.Event()
|
||||||
|
|
||||||
|
try:
|
||||||
|
get_data_API(session, args.league, args.team, args.lang, stop_event)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
# ручное прерывание: просим все рабочие циклы сворачиваться
|
||||||
|
logger.info("KeyboardInterrupt: stopping...")
|
||||||
|
stop_event.set()
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(f"Fatal in main(): {e}")
|
||||||
|
stop_event.set()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user