import os import time from typing import IO, Any, Final from urllib.parse import parse_qs, urlparse if os.name == "nt": import msvcrt elif os.name == "posix": import fcntl class FileLock: # Long enough to outlast a token refresh, which is what the lock is held across. WAIT_SECONDS: Final[float] = 60.0 @staticmethod def lock(file: IO[Any]) -> None: if os.name == "nt": # msvcrt.LK_LOCK gives up after ten one-second attempts, which is shorter # than the refresh it now has to wait for, so do the waiting here instead. deadline = time.monotonic() + FileLock.WAIT_SECONDS while True: try: msvcrt.locking(file.fileno(), msvcrt.LK_NBLCK, 1) return except OSError: if time.monotonic() >= deadline: raise time.sleep(0.1) elif os.name == "posix": fcntl.flock(file.fileno(), fcntl.LOCK_EX) @staticmethod def unlock(file: IO[Any]) -> None: if os.name == "nt": msvcrt.locking(file.fileno(), msvcrt.LK_UNLCK, 1) elif os.name == "posix": fcntl.flock(file.fileno(), fcntl.LOCK_UN) def page_num_from_url(url: str) -> int | None: parsed_url = urlparse(url) params = parse_qs(parsed_url.query) page = params.get("page", [None])[0] return int(page) if page is not None else None