From 504df05898c43a7b897e50762978f0e91dc056f8 Mon Sep 17 00:00:00 2001 From: Brandon Haney <121782102+Brandon-Haney@users.noreply.github.com> Date: Tue, 12 May 2026 15:05:49 -0500 Subject: [PATCH] Warn when an OnDeck file is missing from both cache and array When Plex reports an OnDeck/Watchlist item that is not present at either the cache pool path or /mnt/user0/, _get_move_command silently skipped it while the pre-move SUMMARY still claimed the planned size was cached. Promotes the skip to a WARNING that names both checked paths and the likely cause (non-default cache pool not mounted in the container). Defers the cache SUMMARY/cached_bytes computation until after move_media_files() runs and uses the actual queued bytes (last_cache_moves_bytes) so the SUMMARY line cannot diverge from [RESULTS] Moved to cache. Refs upstream issue #164. --- core/app.py | 39 +++++++++++++++++++++++++++------------ core/file_operations.py | 15 +++++++++++++++ 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/core/app.py b/core/app.py index ac1031ef..2ca9ce37 100644 --- a/core/app.py +++ b/core/app.py @@ -2813,31 +2813,29 @@ def _check_free_space_and_move_files(self, media_files: List[str], destination: self.logging_manager.add_summary_message( f"{would_prefix}{verb} {total_size:.2f} {total_size_unit} to {destination}" ) - else: - # Track cached bytes for summary - size_multipliers = {'KB': 1024, 'MB': 1024**2, 'GB': 1024**3, 'TB': 1024**4} - self.cached_bytes = int(total_size * size_multipliers.get(total_size_unit, 1)) - verb = "cache" if self.dry_run else "Cached" - self.logging_manager.add_summary_message( - f"{would_prefix}{verb} {total_size:.2f} {total_size_unit}" - ) - + # NOTE: cache summary is deferred until after move_media_files() so + # the byte count reflects what was actually queued for caching, not + # the planned total. Files visible via /mnt/user/ FUSE but missing + # from both the cache pool path and /mnt/user0/ get silently skipped + # by _get_move_command, which used to leave the SUMMARY claiming + # success. See issue #164. + free_space, free_space_unit = self.file_utils.get_free_space( cache_dir if destination == 'cache' else real_source ) - + # Check if enough space # Multipliers convert to KB as base unit (KB=1, MB=1024, GB=1024^2, TB=1024^3) size_multipliers = {'KB': 1, 'MB': 1024, 'GB': 1024**2, 'TB': 1024**3} total_size_kb = total_size * size_multipliers.get(total_size_unit, 1) free_space_kb = free_space * size_multipliers.get(free_space_unit, 1) - + if total_size_kb > free_space_kb: if not self.dry_run: sys.exit(f"Not enough space on {destination} drive.") else: logging.error(f"Not enough space on {destination} drive.") - + self.file_mover.move_media_files( media_files_filtered, destination, self.config_manager.performance.max_concurrent_moves_array, @@ -2845,6 +2843,23 @@ def _check_free_space_and_move_files(self, media_files: List[str], destination: source_map, media_info_map ) + + # Cache summary: use actual queued bytes from move_media_files(). + # If no commands were generated (e.g., all files missing from both + # cache and array — see issue #164), skip the summary line. The + # per-file WARNING in _get_move_command surfaces the cause, and + # [RESULTS] Moved to cache: 0 files reflects the truth. + if destination == 'cache': + actual_bytes = getattr(self.file_mover, 'last_cache_moves_bytes', 0) + if actual_bytes > 0: + actual_size, actual_unit = self.file_utils._convert_bytes_to_readable_size(actual_bytes) + self.cached_bytes = actual_bytes + verb = "cache" if self.dry_run else "Cached" + self.logging_manager.add_summary_message( + f"{would_prefix}{verb} {actual_size:.2f} {actual_unit}" + ) + else: + self.cached_bytes = 0 else: if not self.logging_manager.files_moved: self.logging_manager.summary_messages = ["There were no files to move to any destination."] diff --git a/core/file_operations.py b/core/file_operations.py index 2620740c..8cc3a633 100644 --- a/core/file_operations.py +++ b/core/file_operations.py @@ -4156,6 +4156,7 @@ def __init__(self, real_source: str, cache_dir: str, is_unraid: bool, self._source_map: Dict[str, str] = {} # Track actual moves by destination for accurate reporting self.last_cache_moves_count = 0 + self.last_cache_moves_bytes = 0 # Flag to signal stop to running threads self._stop_requested = False # Hard-link tracking: maps cache file paths to inode numbers for restoration @@ -4237,6 +4238,7 @@ def move_media_files(self, files: List[str], destination: str, # Track actual cache moves for accurate diagnostic reporting if destination == 'cache': self.last_cache_moves_count = len(move_commands) + self.last_cache_moves_bytes = total_bytes # Execute the move commands self._execute_move_commands(move_commands, max_concurrent_moves_array, @@ -4372,6 +4374,19 @@ def _get_move_command(self, destination: str, cache_file_name: str, if not self.debug: self.file_utils.create_directory_with_permissions(cache_path, user_file_name) move = (user_file_name, cache_path) + else: + # File visible to Plex via /mnt/user/ FUSE but missing from + # both the cache pool path and the array-direct path. Common + # cause: the file lives on a cache pool not named 'cache' + # (or not mounted into the container). See issue #164. + logging.warning( + f"[CACHE] Cannot locate file to cache: {os.path.basename(user_file_name)} " + f"(missing from {cache_file_name} and {user_file_name}). " + f"Plex sees it via /mnt/user/ but PlexCache cannot reach the " + f"physical file. If this share uses a non-default cache pool, " + f"mount that pool into the container and set cache_path / " + f"host_cache_path on the matching path mapping." + ) return move def _translate_to_host_path(self, cache_path: str, log_translation: bool = False) -> str: