feat: Implement sync manager and file watcher for automated playlist synchronization
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
import os
|
||||
import threading
|
||||
import asyncio
|
||||
from watchdog.observers.polling import PollingObserver as Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
from app.utils.logger import logger
|
||||
from app.utils.config import server_config
|
||||
from app.utils.sync_manager import sync_manager
|
||||
|
||||
class PlaylistEventHandler(FileSystemEventHandler):
|
||||
def __init__(self):
|
||||
self.debounce_timer = None
|
||||
|
||||
def on_any_event(self, event):
|
||||
# Log all events for debugging (using INFO temporarily to ensure visibility)
|
||||
logger.info(f"[WATCHER-DEBUG] Event detected: {event.event_type} {event.src_path}")
|
||||
|
||||
if event.is_directory:
|
||||
return
|
||||
|
||||
# Filter out noisy events. Only listen to actual changes.
|
||||
# 'opened' and 'closed' (without write) are read events and should be ignored.
|
||||
if event.event_type not in ['created', 'modified', 'deleted', 'moved']:
|
||||
return
|
||||
|
||||
# Ignore temporary files or hidden files if necessary
|
||||
filename = os.path.basename(event.src_path)
|
||||
if filename.startswith('.'):
|
||||
return
|
||||
|
||||
# Prevent feedback loops: if sync is in progress, ignore events (likely caused by the sync itself)
|
||||
if sync_manager.is_syncing:
|
||||
logger.info(f"[WATCHER-DEBUG] Ignoring event {event.event_type} on {event.src_path} because sync is in progress.")
|
||||
return
|
||||
|
||||
logger.info(f"File system event detected and accepted: {event.event_type} {event.src_path}")
|
||||
self.trigger_sync()
|
||||
|
||||
def trigger_sync(self):
|
||||
if self.debounce_timer:
|
||||
self.debounce_timer.cancel()
|
||||
|
||||
# Debounce for 5 seconds to allow multiple file operations to complete
|
||||
self.debounce_timer = threading.Timer(5.0, self.run_sync)
|
||||
self.debounce_timer.start()
|
||||
|
||||
def run_sync(self):
|
||||
logger.info("Triggering sync due to file change...")
|
||||
sync_manager.run_sync(trigger_source="watcher", wait=False)
|
||||
|
||||
class WatcherManager:
|
||||
def __init__(self):
|
||||
self.observer = None
|
||||
self.handler = None
|
||||
self.current_path = None
|
||||
|
||||
def start(self, path):
|
||||
# If already watching the same path, do nothing
|
||||
if self.observer and self.observer.is_alive() and self.current_path == path:
|
||||
logger.info(f"Watcher already running on {path}")
|
||||
return
|
||||
|
||||
self.stop()
|
||||
|
||||
if not os.path.exists(path):
|
||||
logger.warning(f"Cannot watch path {path}: Directory does not exist.")
|
||||
return
|
||||
|
||||
logger.info(f"Starting file watcher on: {path}")
|
||||
try:
|
||||
files = os.listdir(path)
|
||||
logger.info(f"Files currently in watch directory: {files}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to list files in watch directory: {e}")
|
||||
|
||||
self.handler = PlaylistEventHandler()
|
||||
# Explicitly set timeout for PollingObserver
|
||||
self.observer = Observer(timeout=1.0)
|
||||
self.observer.schedule(self.handler, path, recursive=True)
|
||||
self.observer.start()
|
||||
self.current_path = path
|
||||
|
||||
def stop(self):
|
||||
if self.observer:
|
||||
logger.info("Stopping file watcher...")
|
||||
self.observer.stop()
|
||||
self.observer.join()
|
||||
self.observer = None
|
||||
self.current_path = None
|
||||
|
||||
watcher_manager = WatcherManager()
|
||||
Reference in New Issue
Block a user