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 asyncio
import re import re
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
import aiodocker
import aiohttp import aiohttp
import random import random
import string import string
@@ -152,50 +151,73 @@ async def main():
""" """
Monitors Navidrome container logs for rapid star/unstar events. Monitors Navidrome container logs for rapid star/unstar events.
""" """
docker = None session = None
try: try:
# Set a long client timeout to prevent log stream from timing out during connector = aiohttp.UnixConnector(path="/var/run/docker.sock")
# periods of inactivity. One day in seconds. # Disable all timeouts for the long-polling log stream
docker = aiodocker.Docker(client_timeout=86400) timeout = aiohttp.ClientTimeout(total=None, sock_read=None)
container = await docker.containers.get(settings.NAVIDROME_CONTAINER) session = aiohttp.ClientSession(connector=connector, timeout=timeout)
logging.info(f"Monitoring logs for container '{settings.NAVIDROME_CONTAINER}'...") container_name = settings.NAVIDROME_CONTAINER
logs = container.log(
stdout=True,
stderr=True,
follow=True,
since=datetime.now(timezone.utc).timestamp(),
)
async for line in logs: # First, check if the container exists.
parsed = parse_log_line(line) inspect_url = f"http://localhost/containers/{container_name}/json"
if not parsed: async with session.get(inspect_url) as response:
continue 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: timestamp, song_id, is_starred = parsed
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 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: if e.status == 404:
logging.error(f"Container '{settings.NAVIDROME_CONTAINER}' not found.") logging.error(f"Container '{settings.NAVIDROME_CONTAINER}' not found.")
else: 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: except Exception:
logging.exception("An unexpected error occurred") logging.exception("An unexpected error occurred")
finally: finally:
if docker: if session:
await docker.close() await session.close()
if __name__ == "__main__": if __name__ == "__main__":