fix: prevent orphaned folder creation and improve cleanup
All checks were successful
Docker Build / build (push) Successful in 13s

This commit is contained in:
wander 2026-03-08 04:29:37 -04:00
parent 8a9f8fbb35
commit dd25df4bdc

View file

@ -344,53 +344,55 @@ def fetch_all_metadata():
def cleanup_old_folders(): def cleanup_old_folders():
""" """
Scans TARGET_DIR for folders containing '+00:00'. Scans both TARGET_DIR and HIDDEN_DIR for empty or orphaned folders.
Safely deletes them ONLY if they contain no real files (only symlinks or empty). Safely deletes them if they contain no real files.
""" """
log("🧹 Starting cleanup. Scanning ONLY for folders containing '+00:00'...") log("🧹 Starting aggressive cleanup of empty folders...")
cleaned_count = 0 cleaned_count = 0
skipped_count = 0
if not TARGET_DIR.exists(): for root in [TARGET_DIR, HIDDEN_DIR]:
return if not root.exists():
# Walk top-down
for channel_dir in TARGET_DIR.iterdir():
if not channel_dir.is_dir():
continue continue
for video_dir in channel_dir.iterdir(): # Walk top-down: Channels
if not video_dir.is_dir(): for channel_dir in root.iterdir():
if not channel_dir.is_dir():
continue continue
if "+00:00" in video_dir.name: # Videos
# Check safety for video_dir in list(channel_dir.iterdir()): # List to allow removal
if not video_dir.is_dir():
continue
# Check if it contains any real files
safe_to_delete = True safe_to_delete = True
reason = ""
for item in video_dir.iterdir(): for item in video_dir.iterdir():
if not item.is_symlink(): if not item.is_symlink():
# Found a real file! Unsafe!
safe_to_delete = False safe_to_delete = False
reason = "Contains real files"
break break
if safe_to_delete: if safe_to_delete:
try: try:
# Remove all symlinks first # Remove all symlinks first
for item in video_dir.iterdir(): for item in list(video_dir.iterdir()):
item.unlink() item.unlink()
# Remove directory # Remove video directory
video_dir.rmdir() video_dir.rmdir()
log(f" [DELETED] {video_dir.name}") log(f" [DELETED VIDEO] {video_dir.name}")
cleaned_count += 1 cleaned_count += 1
except Exception as e: except Exception as e:
log(f" ❌ Failed to delete {video_dir.name}: {e}") pass # Likely not empty
else:
log(f" ⚠️ SKIPPING {video_dir.name} - {reason}") # After cleaning videos, try to clean the channel dir itself if empty
skipped_count += 1 try:
if channel_dir.exists() and not any(channel_dir.iterdir()):
channel_dir.rmdir()
log(f" [DELETED CHANNEL] {channel_dir.name}")
cleaned_count += 1
except Exception:
pass
log(f"🧹 Cleanup complete. Removed: {cleaned_count}, Skipped: {skipped_count}") log(f"🧹 Cleanup complete. Removed {cleaned_count} empty/orphaned directories.")
def check_orphaned_links(): def check_orphaned_links():
""" """
@ -774,103 +776,82 @@ def process_videos():
meta = video_map.get(video_id) meta = video_map.get(video_id)
if not meta: if not meta:
continue continue
sanitized_channel_name = sanitize(meta["channel_name"])
# Determine target root
is_hidden = meta["channel_name"] in hidden_channels
target_root = HIDDEN_DIR if is_hidden else TARGET_DIR
other_root = TARGET_DIR if is_hidden else HIDDEN_DIR
# Check if channel exists in the WRONG place and MOVE it (Migration/Toggle)
wrong_channel_dir = other_root / sanitized_channel_name
correct_channel_dir = target_root / sanitized_channel_name
# DEBUG LOGGING (Temporary)
if meta["channel_name"] in hidden_channels:
log(f"DEBUG: Checking {meta['channel_name']} (Hidden). Wrong Dir: {wrong_channel_dir}, Exists? {wrong_channel_dir.exists()}")
if wrong_channel_dir.exists():
try:
# If destination already exists, we have a conflict.
# Strategy: Merge move?
# Simplest robust way:
# 1. Ensure dest exists
# 2. Move contents?
# Or just shutil.move(src, dst) which works if dst doesn't exist.
if not correct_channel_dir.exists(): sanitized_channel_name = sanitize(meta["channel_name"])
shutil.move(str(wrong_channel_dir), str(correct_channel_dir)) is_hidden = meta["channel_name"] in hidden_channels
log(f" [MOVE] Moved {sanitized_channel_name} to {target_root.name} (Status Change)") target_root = HIDDEN_DIR if is_hidden else TARGET_DIR
else: other_root = TARGET_DIR if is_hidden else HIDDEN_DIR
# Destination exists. We must merge.
# Move items one by one. # Migration Logic
for item in wrong_channel_dir.iterdir(): wrong_channel_dir = other_root / sanitized_channel_name
dest_item = correct_channel_dir / item.name correct_channel_dir = target_root / sanitized_channel_name
if not dest_item.exists():
shutil.move(str(item), str(dest_item)) if wrong_channel_dir.exists():
else: try:
# Conflict. If it's a folder, we could recurse, but let's just log warning and skip? if not correct_channel_dir.exists():
# If it's a file/symlink, we skip (it will be regenerated/verified later by the loop) shutil.move(str(wrong_channel_dir), str(correct_channel_dir))
pass log(f" [MOVE] Migrated {sanitized_channel_name} to {target_root.name}")
# Now remove the empty source dir
try:
wrong_channel_dir.rmdir()
except OSError:
log(f" ⚠️ Could not remove old dir {wrong_channel_dir} (not empty?)")
except Exception as e:
log(f" ❌ Failed to move {sanitized_channel_name} from old location: {e}")
channel_dir = target_root / sanitized_channel_name
channel_dir.mkdir(parents=True, exist_ok=True)
sanitized_title = sanitize(meta["title"])
folder_name = f"{meta['published']} - {sanitized_title}"
video_dir = channel_dir / folder_name
video_dir.mkdir(parents=True, exist_ok=True)
actual_file = next(channel_path.glob(f"{video_id}.*"), None)
if not actual_file:
continue
host_path_root = Path("/mnt/user/tubearchives/bp")
host_source_path = host_path_root / actual_file.relative_to(SOURCE_DIR)
dest_file = video_dir / f"video{actual_file.suffix}"
try:
if dest_file.exists():
if dest_file.is_symlink():
current_target = Path(os.readlink(dest_file))
if current_target.resolve() != host_source_path.resolve():
dest_file.unlink()
os.symlink(host_source_path, dest_file)
log(f" [FIX] Relinked: {folder_name}")
new_links += 1
else: else:
verified_links += 1 for item in list(wrong_channel_dir.iterdir()):
dest_item = correct_channel_dir / item.name
if not dest_item.exists():
shutil.move(str(item), str(dest_item))
try:
wrong_channel_dir.rmdir()
except OSError:
pass
except Exception as e:
log(f" ❌ Migration error for {sanitized_channel_name}: {e}")
# Folder Creation & Linking
channel_dir = target_root / sanitized_channel_name
sanitized_title = sanitize(meta["title"])
folder_name = f"{meta['published']} - {sanitized_title}"
video_dir = channel_dir / folder_name
# IMPORTANT: mkdir only when we are sure we have the file
# actual_file is video_file
channel_dir.mkdir(parents=True, exist_ok=True)
video_dir.mkdir(parents=True, exist_ok=True)
host_path_root = Path("/mnt/user/tubearchives/bp")
host_source_path = host_path_root / video_file.relative_to(SOURCE_DIR)
dest_file = video_dir / f"video{video_file.suffix}"
try:
if dest_file.exists():
if dest_file.is_symlink():
current_target = Path(os.readlink(dest_file))
if current_target.resolve() != host_source_path.resolve():
dest_file.unlink()
os.symlink(host_source_path, dest_file)
log(f" [FIX] Relinked: {folder_name}")
new_links += 1
else:
verified_links += 1
else: else:
# It's a file or something else, replace it? No, unsafe. os.symlink(host_source_path, dest_file)
pass log(f" [NEW] Linked: {folder_name}")
else: new_links += 1
os.symlink(host_source_path, dest_file) except Exception as e:
log(f" [NEW] Linked: {folder_name}") log(f" ❌ Link error for {folder_name}: {e}")
new_links += 1
except Exception: # Store in database
pass conn.execute("""
INSERT OR REPLACE INTO videos
# Store in database (video_id, title, channel, published, symlink, status)
conn.execute(""" VALUES (?, ?, ?, ?, ?, 'linked')
INSERT OR REPLACE INTO videos """, (video_id, meta["title"], meta["channel_name"],
(video_id, title, channel, published, symlink, status) meta["published"], str(dest_file)))
VALUES (?, ?, ?, ?, ?, 'linked')
""", (video_id, meta["title"], meta["channel_name"], processed_videos.append({
meta["published"], str(dest_file))) "video_id": video_id,
"title": meta["title"],
processed_videos.append({ "channel": meta["channel_name"],
"video_id": video_id, "published": meta["published"],
"title": meta["title"], "symlink": str(dest_file)
"channel": meta["channel_name"], })
"published": meta["published"],
"symlink": str(dest_file)
})
except Exception as e: except Exception as e:
conn.rollback() conn.rollback()
return str(e) return str(e)