v0.5.84: matches origin filter, mobile gate, refresh reliability.
Ship Web refresh cache/lock, mobile demand gate, matches 职业/国服 filter, and related site updates through 0.5.84. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,6 +7,8 @@ timeout, and retry conventions live in exactly one place.
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
@@ -22,6 +24,36 @@ HEROES_URL = "https://www.dota2.com/datafeed/herolist?language={lang}"
|
||||
ITEMLIST_URL = "https://www.dota2.com/datafeed/itemlist?language={lang}"
|
||||
|
||||
|
||||
def write_json_atomic(
|
||||
path: Path,
|
||||
payload,
|
||||
*,
|
||||
indent: int | None = 2,
|
||||
separators: tuple[str, str] | None = None,
|
||||
) -> None:
|
||||
"""Atomically replace a UTF-8 JSON file after flushing it to disk."""
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
text = json.dumps(
|
||||
payload,
|
||||
ensure_ascii=False,
|
||||
indent=indent,
|
||||
separators=separators,
|
||||
)
|
||||
if indent is not None:
|
||||
text += "\n"
|
||||
fd, tmp_name = tempfile.mkstemp(prefix=f".{path.name}.", suffix=".tmp", dir=path.parent)
|
||||
tmp = Path(tmp_name)
|
||||
try:
|
||||
with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as f:
|
||||
f.write(text)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
os.replace(tmp, path)
|
||||
except BaseException:
|
||||
tmp.unlink(missing_ok=True)
|
||||
raise
|
||||
|
||||
|
||||
def _http_open(url: str, *, timeout: int, retries: int, backoff: float):
|
||||
"""urlopen with retries on 429 / 5xx / transient network errors."""
|
||||
last_err: BaseException | None = None
|
||||
|
||||
Reference in New Issue
Block a user