diff --git a/watcher.py b/watcher.py index b00744a..bb9e898 100644 --- a/watcher.py +++ b/watcher.py @@ -1,96 +1,114 @@ #!/usr/bin/env python3 -import sys +import os import time import threading -import os import subprocess +import logging +from logging.handlers import TimedRotatingFileHandler from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler -DEBOUNCE_DELAY = 2 # seconds to wait after last modification +# Configuration +WATCH_DIR = os.environ.get("WATCH_DIR", "/data") +DEBOUNCE_DELAY = int(os.environ.get("DEBOUNCE_DELAY", "2")) DEBUG = os.environ.get("DEBUG", "false").lower() == "true" +LOG_DIR = os.path.join(WATCH_DIR, "/logs") +os.makedirs(LOG_DIR, exist_ok=True) + +# Set up logging with monthly rollover +log_file_path = os.path.join(LOG_DIR, "watcher.log") +logger = logging.getLogger("WatcherLogger") +logger.setLevel(logging.DEBUG) +formatter = logging.Formatter("[%(asctime)s] %(levelname)s: %(message)s", "%Y-%m-%d %H:%M:%S") + +# Timed rotating handler (monthly) +handler = TimedRotatingFileHandler(log_file_path, when="midnight", interval=1) +handler.suffix = "%Y-%m" # files will be like watcher.log.2025-09 +handler.setFormatter(formatter) +logger.addHandler(handler) + +# Optionally log to console if DEBUG +if DEBUG: + console_handler = logging.StreamHandler() + console_handler.setFormatter(formatter) + logger.addHandler(console_handler) class WatcherHandler(FileSystemEventHandler): def __init__(self): super().__init__() - self.debounce_timers = {} # path -> Timer - self.processed_folders = set() # already processed folders + self.debounce_timers = {} + self.processed_folders = set() def on_any_event(self, event): - # Print all events if event.event_type == "moved": - print(f"{'DIR' if event.is_directory else 'FILE'} MOVED: {event.src_path} -> {event.dest_path}") + logger.info(f"{'DIR' if event.is_directory else 'FILE'} MOVED: {event.src_path} -> {event.dest_path}") else: - print(f"{'DIR' if event.is_directory else 'FILE'} {event.event_type.upper()}: {event.src_path}") + logger.info(f"{'DIR' if event.is_directory else 'FILE'} {event.event_type.upper()}: {event.src_path}") - # Only schedule processing for directories if event.is_directory and event.event_type in ("created", "moved", "modified"): path_to_process = event.dest_path if event.event_type == "moved" else event.src_path self.schedule_process(path_to_process) def schedule_process(self, path): - """Debounce processing of a folder.""" if path in self.debounce_timers: self.debounce_timers[path].cancel() - timer = threading.Timer(DEBOUNCE_DELAY, self.process_new_dir, args=[path]) self.debounce_timers[path] = timer timer.start() def process_new_dir(self, path): - """Called after folder is stable for DEBOUNCE_DELAY seconds.""" if path in self.debounce_timers: del self.debounce_timers[path] if not os.path.exists(path): - if DEBUG: - print(f"⚠️ Skipping {path}, folder no longer exists") + logger.warning(f"Skipping {path}, folder no longer exists") + return + + folder_name = os.path.basename(path) + if "Books 1-" in folder_name: + logger.info(f"Skipping folder {folder_name} due to 'Books 1-'") return - # Check if folder has .m4b files m4b_files = [ f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f)) and f.lower().endswith(".m4b") and not f.startswith("._") ] if not m4b_files: - if DEBUG: - print(f"❌ No .m4b files in {path}, skipping") + logger.info(f"No .m4b files in {path}, skipping") return - # Avoid re-processing the same folder if path in self.processed_folders: - if DEBUG: - print(f"⚠️ Folder already processed: {path}") + logger.info(f"Folder already processed: {path}") return self.processed_folders.add(path) - print(f"📂 Folder ready: {path} contains {len(m4b_files)} .m4b file(s)") + logger.info(f"Folder ready: {path} contains {len(m4b_files)} .m4b file(s)") - # Run main.py -da try: - subprocess.run(["python3", "main.py", "-da", path], check=True) - print(f"✅ Processed folder: {path}") + result = subprocess.run( + ["python3", "main.py", "-da", path], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True + ) + logger.info(result.stdout) + logger.info(f"Processed folder: {path}") except subprocess.CalledProcessError as e: - print(f"❌ Error processing folder {path}: {e}") + logger.error(f"Error processing folder {path}:\n{e.output if hasattr(e, 'output') else e}") def main(): - if len(sys.argv) < 2: - print(f"Usage: {sys.argv[0]} /path/to/watch") - sys.exit(1) - - watch_path = sys.argv[1] - - if not os.path.exists(watch_path): - print(f"❌ Path does not exist: {watch_path}") - sys.exit(1) + if not os.path.exists(WATCH_DIR): + logger.error(f"Path does not exist: {WATCH_DIR}") + return event_handler = WatcherHandler() observer = Observer() - observer.schedule(event_handler, watch_path, recursive=True) + observer.schedule(event_handler, WATCH_DIR, recursive=True) observer.start() - print(f"👀 Watching: {watch_path}") + logger.info(f"Watching: {WATCH_DIR}") try: while True: time.sleep(1) @@ -98,6 +116,5 @@ def main(): observer.stop() observer.join() - if __name__ == "__main__": main()