Refactor OAuth provider linking and unlinking logic into a dedicated service; enhance error handling and logging throughout the application; improve sound management and scanning services with better file handling and unique naming; implement centralized error and logging services for consistent API responses and application-wide logging configuration.

This commit is contained in:
JSC
2025-07-05 13:07:06 +02:00
parent 41fc197f4c
commit e2fe451e5a
17 changed files with 758 additions and 352 deletions

View File

@@ -1,15 +1,15 @@
"""VLC service for playing sounds using subprocess."""
import os
import signal
import subprocess
import threading
import time
from typing import Dict, List, Optional
from app.database import db
from app.models.sound import Sound
from app.models.sound_played import SoundPlayed
from app.services.logging_service import LoggingService
logger = LoggingService.get_logger(__name__)
class VLCService:
@@ -17,7 +17,7 @@ class VLCService:
def __init__(self) -> None:
"""Initialize VLC service."""
self.processes: Dict[str, subprocess.Popen] = {}
self.processes: dict[str, subprocess.Popen] = {}
self.lock = threading.Lock()
def play_sound(self, sound_id: int, user_id: int | None = None) -> bool:
@@ -38,7 +38,9 @@ class VLCService:
)
else:
sound_path = os.path.join(
"sounds", "soundboard", sound.filename
"sounds",
"soundboard",
sound.filename,
)
# Check if file exists
@@ -73,8 +75,9 @@ class VLCService:
with self.lock:
self.processes[process_id] = process
print(
f"Started VLC process {process.pid} ({process_id}) for sound {sound.name}. Total processes: {len(self.processes)}"
logger.info(
f"Started VLC process {process.pid} for sound '{sound.name}'. "
f"Total active processes: {len(self.processes)}",
)
# Increment play count
@@ -89,7 +92,7 @@ class VLCService:
commit=True,
)
except Exception as e:
print(f"Error recording play event: {e}")
logger.error(f"Error recording play event: {e}")
# Schedule cleanup after sound duration
threading.Thread(
@@ -101,7 +104,9 @@ class VLCService:
return True
except Exception as e:
print(f"Error starting VLC process for sound {sound_id}: {e}")
logger.error(
f"Error starting VLC process for sound {sound_id}: {e}"
)
return False
def _cleanup_after_playback(self, process_id: str, duration: int) -> None:
@@ -111,13 +116,13 @@ class VLCService:
with self.lock:
if process_id in self.processes:
print(f"Cleaning up process {process_id} after playback")
logger.debug(f"Cleaning up process {process_id} after playback")
process = self.processes[process_id]
try:
# Check if process is still running
if process.poll() is None:
print(
logger.debug(
f"Process {process.pid} still running, terminating"
)
process.terminate()
@@ -125,62 +130,58 @@ class VLCService:
try:
process.wait(timeout=2)
except subprocess.TimeoutExpired:
print(
logger.debug(
f"Process {process.pid} didn't terminate, killing"
)
process.kill()
print(f"Successfully cleaned up process {process_id}")
logger.debug(
f"Successfully cleaned up process {process_id}"
)
except Exception as e:
print(f"Error during cleanup of {process_id}: {e}")
logger.warning(f"Error during cleanup of {process_id}: {e}")
finally:
# Always remove from tracking
del self.processes[process_id]
print(
f"Removed process {process_id}. Remaining processes: {len(self.processes)}"
logger.debug(
f"Removed process {process_id}. Remaining processes: {len(self.processes)}",
)
else:
print(f"Process {process_id} not found during cleanup")
def stop_all(self) -> None:
"""Stop all playing sounds by killing VLC processes."""
with self.lock:
processes_copy = dict(self.processes)
print(
f"Stopping {len(processes_copy)} VLC processes: {list(processes_copy.keys())}"
)
if processes_copy:
logger.info(f"Stopping {len(processes_copy)} VLC processes")
for process_id, process in processes_copy.items():
try:
if process.poll() is None: # Process is still running
print(
f"Terminating process {process.pid} ({process_id})"
)
logger.debug(f"Terminating process {process.pid}")
process.terminate()
# Give it a moment to terminate gracefully
try:
process.wait(timeout=1)
print(
logger.debug(
f"Process {process.pid} terminated gracefully"
)
except subprocess.TimeoutExpired:
print(
logger.debug(
f"Process {process.pid} didn't terminate, killing forcefully"
)
process.kill()
process.wait() # Wait for it to be killed
else:
print(
f"Process {process.pid} ({process_id}) already finished"
)
logger.debug(f"Process {process.pid} already finished")
except Exception as e:
print(f"Error stopping process {process_id}: {e}")
logger.warning(f"Error stopping process {process_id}: {e}")
# Clear all processes
self.processes.clear()
print(f"Cleared all processes. Remaining: {len(self.processes)}")
if processes_copy:
logger.info("All VLC processes stopped")
def get_playing_count(self) -> int:
"""Get number of currently playing sounds."""
@@ -201,34 +202,20 @@ class VLCService:
"""Force stop all sounds by killing VLC processes aggressively."""
with self.lock:
stopped_count = len(self.processes)
print(f"Force stopping {stopped_count} VLC processes")
# # Kill all VLC processes aggressively
# for process_id, process in list(self.processes.items()):
# try:
# if process.poll() is None: # Process is still running
# print(f"Force killing process {process.pid} ({process_id})")
# process.kill()
# process.wait() # Wait for it to be killed
# print(f"Process {process.pid} killed")
# else:
# print(f"Process {process.pid} ({process_id}) already finished")
# except Exception as e:
# print(f"Error force-stopping process {process_id}: {e}")
if stopped_count > 0:
logger.warning(f"Force stopping {stopped_count} VLC processes")
# Also try to kill any remaining VLC processes system-wide
try:
subprocess.run(["pkill", "-f", "vlc"], check=False)
print("Killed any remaining VLC processes system-wide")
logger.info("Killed any remaining VLC processes system-wide")
except Exception as e:
print(f"Error killing system VLC processes: {e}")
logger.error(f"Error killing system VLC processes: {e}")
# Clear all processes
self.processes.clear()
print(
f"Force stop completed. Processes remaining: {len(self.processes)}"
)
if stopped_count > 0:
logger.info("Force stop completed")
return stopped_count