Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 27 additions & 12 deletions core/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2813,38 +2813,53 @@ 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,
self.config_manager.performance.max_concurrent_moves_cache,
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."]
Expand Down
15 changes: 15 additions & 0 deletions core/file_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down
Loading