Files
ripe-cidr-collector/storage.py
T
ayurishchevandClaude Sonnet 5 bcf8156085 Initial commit: RIPE CIDR/FQDN collector
Collector daemon, FastAPI server (addresses, diff, collect, sources),
SQLite storage with change journal, Docker Compose deployment,
tests, documentation and project rules.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 07:29:38 +03:00

71 lines
2.3 KiB
Python

import fcntl
import json
import logging
import os
import tempfile
import time
from contextlib import contextmanager
logger = logging.getLogger(__name__)
class StorageError(Exception):
"""Файл данных повреждён или недоступен."""
def load_json(path, default):
"""Читает JSON. Битый файл переименовывается в *.corrupt-<ts>, чтобы его не затёрли."""
if not os.path.exists(path):
return default
try:
with open(path, 'r') as f:
return json.load(f)
except json.JSONDecodeError as e:
backup = f"{path}.corrupt-{int(time.time())}"
os.replace(path, backup)
logger.error("Corrupted JSON in %s (%s); moved to %s", path, e, backup)
raise StorageError(f"{path} is corrupted") from e
except OSError as e:
raise StorageError(f"Cannot read {path}: {e}") from e
def save_json_atomic(path, data):
"""Пишет во временный файл в том же каталоге и подменяет оригинал через os.replace."""
directory = os.path.dirname(os.path.abspath(path))
fd, tmp_path = tempfile.mkstemp(dir=directory, prefix=os.path.basename(path) + ".", suffix=".tmp")
try:
with os.fdopen(fd, 'w') as f:
json.dump(data, f, indent=4)
f.flush()
os.fsync(f.fileno())
os.replace(tmp_path, path)
except BaseException:
if os.path.exists(tmp_path):
os.unlink(tmp_path)
raise
@contextmanager
def file_lock(path):
"""Межпроцессная блокировка (сборщик, CLI и API) через <path>.lock."""
with open(path + ".lock", 'w') as lock_file:
fcntl.flock(lock_file, fcntl.LOCK_EX)
try:
yield
finally:
fcntl.flock(lock_file, fcntl.LOCK_UN)
def try_lock(path):
"""Неблокирующая эксклюзивная блокировка (singleton-процесс).
Возвращает открытый файл (блокировка держится, пока он жив) или None, если занято.
"""
lock_file = open(path, 'w')
try:
fcntl.flock(lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError:
lock_file.close()
return None
return lock_file