2025-11-30 14:45:55 +01:00
|
|
|
"""Utility functions for the Lichess bot."""
|
|
|
|
|
|
2025-08-22 16:49:30 +02:00
|
|
|
import logging
|
2025-08-22 20:22:06 +02:00
|
|
|
import os
|
2025-08-22 16:49:30 +02:00
|
|
|
import time
|
|
|
|
|
|
2025-11-30 21:59:24 +01:00
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
2025-08-22 16:49:30 +02:00
|
|
|
|
2025-08-22 20:22:06 +02:00
|
|
|
def _version_file_path() -> str:
|
|
|
|
|
"""Return the path to the persistent bot version file.
|
|
|
|
|
|
|
|
|
|
Stored alongside this module as a simple text file containing an integer.
|
|
|
|
|
"""
|
|
|
|
|
override = os.getenv("LICHESS_BOT_VERSION_FILE")
|
|
|
|
|
if override:
|
|
|
|
|
return override
|
|
|
|
|
return os.path.join(os.path.dirname(__file__), ".bot_version")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_and_increment_version() -> int:
|
|
|
|
|
"""Read the current bot version, increment it, persist, and return the new version.
|
|
|
|
|
|
|
|
|
|
If the version file doesn't exist or is invalid, starts from 0, then sets to 1.
|
|
|
|
|
"""
|
|
|
|
|
path = _version_file_path()
|
|
|
|
|
current = 0
|
|
|
|
|
try:
|
2025-11-30 13:42:16 +01:00
|
|
|
with open(path) as f:
|
2025-08-22 20:22:06 +02:00
|
|
|
raw = f.read().strip()
|
|
|
|
|
if raw:
|
|
|
|
|
current = int(raw)
|
2025-11-30 21:37:47 +01:00
|
|
|
except (OSError, ValueError):
|
2025-08-22 20:22:06 +02:00
|
|
|
# Missing or unreadable file -> treat as version 0
|
|
|
|
|
current = 0
|
|
|
|
|
|
|
|
|
|
new_version = current + 1
|
|
|
|
|
try:
|
|
|
|
|
tmp_path = path + ".tmp"
|
|
|
|
|
with open(tmp_path, "w") as f:
|
|
|
|
|
f.write(str(new_version))
|
|
|
|
|
os.replace(tmp_path, path)
|
2025-11-30 21:37:47 +01:00
|
|
|
except OSError:
|
2025-08-22 20:22:06 +02:00
|
|
|
# As a fallback, try a direct write; failure is non-fatal to bot operation
|
|
|
|
|
try:
|
|
|
|
|
with open(path, "w") as f:
|
|
|
|
|
f.write(str(new_version))
|
2025-11-30 21:37:47 +01:00
|
|
|
except OSError:
|
2025-11-30 21:59:24 +01:00
|
|
|
_logger.debug("Could not persist bot version to %s", path)
|
2025-08-22 20:22:06 +02:00
|
|
|
|
|
|
|
|
return new_version
|
|
|
|
|
|
|
|
|
|
|
2025-08-22 16:49:30 +02:00
|
|
|
def backoff_sleep(current_backoff: int, base: float = 0.5, cap: float = 8.0) -> int:
|
|
|
|
|
"""Sleep with exponential backoff. Returns the next backoff step.
|
|
|
|
|
|
|
|
|
|
- current_backoff: number of consecutive failures
|
|
|
|
|
- base: base delay in seconds
|
|
|
|
|
- cap: maximum delay in seconds
|
|
|
|
|
"""
|
2025-11-30 13:42:16 +01:00
|
|
|
delay = min(cap, base * (2**current_backoff))
|
2025-11-30 21:59:24 +01:00
|
|
|
_logger.info("Backing off for %.1fs", delay)
|
2025-08-22 16:49:30 +02:00
|
|
|
time.sleep(delay)
|
|
|
|
|
return min(current_backoff + 1, 10)
|