From 0da71b7398612928fb8b9d971ae258f4b58e7e41 Mon Sep 17 00:00:00 2001 From: Tanner Collin Date: Sat, 7 Feb 2026 14:18:54 -0700 Subject: [PATCH] refactor: Remove aiodocker, use aiohttp for Docker REST API Co-authored-by: aider (gemini/gemini-2.5-pro) --- main.py | 88 +++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 55 insertions(+), 33 deletions(-) diff --git a/main.py b/main.py index e653c7b..0c34d11 100644 --- a/main.py +++ b/main.py @@ -7,7 +7,6 @@ logging.basicConfig( import asyncio import re from datetime import datetime, timedelta, timezone -import aiodocker import aiohttp import random import string @@ -152,50 +151,73 @@ async def main(): """ Monitors Navidrome container logs for rapid star/unstar events. """ - docker = None + session = None try: - # Set a long client timeout to prevent log stream from timing out during - # periods of inactivity. One day in seconds. - docker = aiodocker.Docker(client_timeout=86400) - container = await docker.containers.get(settings.NAVIDROME_CONTAINER) + connector = aiohttp.UnixConnector(path="/var/run/docker.sock") + # Disable all timeouts for the long-polling log stream + timeout = aiohttp.ClientTimeout(total=None, sock_read=None) + session = aiohttp.ClientSession(connector=connector, timeout=timeout) - logging.info(f"Monitoring logs for container '{settings.NAVIDROME_CONTAINER}'...") - logs = container.log( - stdout=True, - stderr=True, - follow=True, - since=datetime.now(timezone.utc).timestamp(), - ) + container_name = settings.NAVIDROME_CONTAINER - async for line in logs: - parsed = parse_log_line(line) - if not parsed: - continue + # First, check if the container exists. + inspect_url = f"http://localhost/containers/{container_name}/json" + async with session.get(inspect_url) as response: + if response.status == 404: + logging.error(f"Container '{container_name}' not found.") + return + response.raise_for_status() - timestamp, song_id, is_starred = parsed + logging.info(f"Monitoring logs for container '{container_name}'...") + since = int(datetime.now(timezone.utc).timestamp()) + params = { + 'stdout': 'true', + 'stderr': 'true', + 'follow': 'true', + 'since': str(since), + } + logs_url = f"http://localhost/containers/{container_name}/logs" + async with session.get(logs_url, params=params) as response: + response.raise_for_status() + while True: + line_bytes = await response.content.readline() + if not line_bytes: + break + # Docker's log stream is multiplexed. The first 8 bytes are a header. + # We strip it to get the raw log line. + if len(line_bytes) > 8: + line = line_bytes[8:].decode('utf-8', errors='replace').strip() + parsed = parse_log_line(line) + if not parsed: + continue - if is_starred: - starred_songs[song_id] = timestamp - elif song_id in starred_songs: # is_starred is False (unstarred) - time_diff = timestamp - starred_songs[song_id] - if time_diff <= STAR_UNSTAR_WINDOW: - logging.info( - f"Song {song_id} was starred and then unstarred within {STAR_UNSTAR_WINDOW.seconds} seconds." - ) - await handle_star_unstar_event(song_id) - # Remove song from tracking after it has been unstarred - del starred_songs[song_id] + timestamp, song_id, is_starred = parsed - except aiodocker.exceptions.DockerError as e: + if is_starred: + starred_songs[song_id] = timestamp + elif song_id in starred_songs: # is_starred is False (unstarred) + time_diff = timestamp - starred_songs[song_id] + if time_diff <= STAR_UNSTAR_WINDOW: + logging.info( + f"Song {song_id} was starred and then unstarred within {STAR_UNSTAR_WINDOW.seconds} seconds." + ) + await handle_star_unstar_event(song_id) + # Remove song from tracking after it has been unstarred + del starred_songs[song_id] + + except aiohttp.ClientResponseError as e: if e.status == 404: logging.error(f"Container '{settings.NAVIDROME_CONTAINER}' not found.") else: - logging.error(f"Error connecting to Docker or getting container: {e}") + logging.error(f"Error with Docker API: {e}") + except aiohttp.ClientError as e: + # For other client errors like connection issues + logging.error(f"Error connecting to Docker: {e}") except Exception: logging.exception("An unexpected error occurred") finally: - if docker: - await docker.close() + if session: + await session.close() if __name__ == "__main__":