Update watcher.py

This commit is contained in:
TylerCG 2025-09-13 21:59:56 -04:00
parent b546e73a1f
commit 031322466b

View File

@ -1,96 +1,114 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import sys import os
import time import time
import threading import threading
import os
import subprocess import subprocess
import logging
from logging.handlers import TimedRotatingFileHandler
from watchdog.observers import Observer from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler 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" 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): class WatcherHandler(FileSystemEventHandler):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.debounce_timers = {} # path -> Timer self.debounce_timers = {}
self.processed_folders = set() # already processed folders self.processed_folders = set()
def on_any_event(self, event): def on_any_event(self, event):
# Print all events
if event.event_type == "moved": 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: 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"): 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 path_to_process = event.dest_path if event.event_type == "moved" else event.src_path
self.schedule_process(path_to_process) self.schedule_process(path_to_process)
def schedule_process(self, path): def schedule_process(self, path):
"""Debounce processing of a folder."""
if path in self.debounce_timers: if path in self.debounce_timers:
self.debounce_timers[path].cancel() self.debounce_timers[path].cancel()
timer = threading.Timer(DEBOUNCE_DELAY, self.process_new_dir, args=[path]) timer = threading.Timer(DEBOUNCE_DELAY, self.process_new_dir, args=[path])
self.debounce_timers[path] = timer self.debounce_timers[path] = timer
timer.start() timer.start()
def process_new_dir(self, path): def process_new_dir(self, path):
"""Called after folder is stable for DEBOUNCE_DELAY seconds."""
if path in self.debounce_timers: if path in self.debounce_timers:
del self.debounce_timers[path] del self.debounce_timers[path]
if not os.path.exists(path): if not os.path.exists(path):
if DEBUG: logger.warning(f"Skipping {path}, folder no longer exists")
print(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 return
# Check if folder has .m4b files
m4b_files = [ m4b_files = [
f for f in os.listdir(path) 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 os.path.isfile(os.path.join(path, f)) and f.lower().endswith(".m4b") and not f.startswith("._")
] ]
if not m4b_files: if not m4b_files:
if DEBUG: logger.info(f"No .m4b files in {path}, skipping")
print(f"❌ No .m4b files in {path}, skipping")
return return
# Avoid re-processing the same folder
if path in self.processed_folders: if path in self.processed_folders:
if DEBUG: logger.info(f"Folder already processed: {path}")
print(f"⚠️ Folder already processed: {path}")
return return
self.processed_folders.add(path) 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: try:
subprocess.run(["python3", "main.py", "-da", path], check=True) result = subprocess.run(
print(f"✅ Processed folder: {path}") ["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: 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(): def main():
if len(sys.argv) < 2: if not os.path.exists(WATCH_DIR):
print(f"Usage: {sys.argv[0]} /path/to/watch") logger.error(f"Path does not exist: {WATCH_DIR}")
sys.exit(1) return
watch_path = sys.argv[1]
if not os.path.exists(watch_path):
print(f"❌ Path does not exist: {watch_path}")
sys.exit(1)
event_handler = WatcherHandler() event_handler = WatcherHandler()
observer = Observer() observer = Observer()
observer.schedule(event_handler, watch_path, recursive=True) observer.schedule(event_handler, WATCH_DIR, recursive=True)
observer.start() observer.start()
print(f"👀 Watching: {watch_path}") logger.info(f"Watching: {WATCH_DIR}")
try: try:
while True: while True:
time.sleep(1) time.sleep(1)
@ -98,6 +116,5 @@ def main():
observer.stop() observer.stop()
observer.join() observer.join()
if __name__ == "__main__": if __name__ == "__main__":
main() main()