From f826c4f0089bc28725bb3ec68014d865e8dd824e Mon Sep 17 00:00:00 2001 From: Mark Hoekveen Date: Wed, 18 Dec 2024 19:52:17 +0100 Subject: [PATCH] adds file locking --- bin/vcmp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/bin/vcmp b/bin/vcmp index 015e1dc..b130859 100755 --- a/bin/vcmp +++ b/bin/vcmp @@ -13,6 +13,31 @@ video_extensions: list[str] = ["mp4", "mkv", "avi", "wmv", "mov", "m4v", "ts", " total_files: int = 0 +def acquire_file_lock(filepath: pathlib.Path) -> bool: + lock_file = filepath.with_suffix(filepath.suffix + '.lock') + try: + # Atomic file creation + lock_fd = os.open(str(lock_file), os.O_CREAT | os.O_EXCL | os.O_WRONLY) + with os.fdopen(lock_fd, 'w') as f: + f.write(f"{os.getpid()}\n{time.time()}\n") + return True + except FileExistsError: + try: + lock_age = time.time() - lock_file.stat().st_mtime + if lock_age > 86400: # 24 hours + lock_file.unlink() + return acquire_file_lock(filepath) + except FileNotFoundError: + return acquire_file_lock(filepath) + return False + +def release_file_lock(filepath: pathlib.Path) -> None: + lock_file = filepath.with_suffix(filepath.suffix + '.lock') + try: + lock_file.unlink() + except FileNotFoundError: + pass + def setup_logging(verbose: bool) -> None: """Configure logging to both file and stdout.""" log_level = logging.DEBUG if verbose else logging.INFO @@ -150,6 +175,10 @@ def process_single_file( nice: int, file_index: int ) -> bool: # Return success/failure status + if not acquire_file_lock(filepath): + logging.info(f"Skipping {filepath}: already being processed") + return False + temp_path = temp_dir / filepath.with_suffix(".mp4").name clean_directory(temp_dir) # Should be empty if mktemp was used, but clear it regardless. logging.info(f"Processing {filepath} to {target_path}") @@ -163,11 +192,13 @@ def process_single_file( logging.warning("Process interrupted by user") if temp_path.exists(): temp_path.unlink() + release_file_lock(filepath) raise # Re-raise KeyboardInterrupt to exit the program except subprocess.CalledProcessError as e: logging.error(f"FFmpeg error processing {filepath}: {e}") if temp_path.exists(): temp_path.unlink() + release_file_lock(filepath) return False else: # File successfully converted to av1, move to destination @@ -178,6 +209,7 @@ def process_single_file( if remove_source: logging.info(f"Removing source file {filepath}") filepath.unlink() + release_file_lock(filepath) return True def process_directory(