refactor: Remove aiodocker, use aiohttp for Docker REST API

Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
This commit is contained in:
2026-02-07 14:18:54 -07:00
parent 45c38a8496
commit 0da71b7398

88
main.py
View File

@@ -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__":