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
safe_to_delete = True if not video_dir.is_dir():
reason = "" continue
# Check if it contains any real files
safe_to_delete = True
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}")
skipped_count += 1
log(f"🧹 Cleanup complete. Removed: {cleaned_count}, Skipped: {skipped_count}") # After cleaning videos, try to clean the channel dir itself if empty
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} 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"]) sanitized_channel_name = sanitize(meta["channel_name"])
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
# Determine target root # Migration Logic
is_hidden = meta["channel_name"] in hidden_channels wrong_channel_dir = other_root / sanitized_channel_name
target_root = HIDDEN_DIR if is_hidden else TARGET_DIR correct_channel_dir = target_root / sanitized_channel_name
other_root = TARGET_DIR if is_hidden else HIDDEN_DIR
# Check if channel exists in the WRONG place and MOVE it (Migration/Toggle) if wrong_channel_dir.exists():
wrong_channel_dir = other_root / sanitized_channel_name try:
correct_channel_dir = target_root / sanitized_channel_name if not correct_channel_dir.exists():
shutil.move(str(wrong_channel_dir), str(correct_channel_dir))
# DEBUG LOGGING (Temporary) log(f" [MOVE] Migrated {sanitized_channel_name} to {target_root.name}")
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():
shutil.move(str(wrong_channel_dir), str(correct_channel_dir))
log(f" [MOVE] Moved {sanitized_channel_name} to {target_root.name} (Status Change)")
else:
# Destination exists. We must merge.
# Move items one by one.
for item in wrong_channel_dir.iterdir():
dest_item = correct_channel_dir / item.name
if not dest_item.exists():
shutil.move(str(item), str(dest_item))
else:
# Conflict. If it's a folder, we could recurse, but let's just log warning and skip?
# If it's a file/symlink, we skip (it will be regenerated/verified later by the loop)
pass
# 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:
pass
# Store in database # Store in database
conn.execute(""" conn.execute("""
INSERT OR REPLACE INTO videos INSERT OR REPLACE INTO videos
(video_id, title, channel, published, symlink, status) (video_id, title, channel, published, symlink, status)
VALUES (?, ?, ?, ?, ?, 'linked') VALUES (?, ?, ?, ?, ?, 'linked')
""", (video_id, meta["title"], meta["channel_name"], """, (video_id, meta["title"], meta["channel_name"],
meta["published"], str(dest_file))) meta["published"], str(dest_file)))
processed_videos.append({ processed_videos.append({
"video_id": video_id, "video_id": video_id,
"title": meta["title"], "title": meta["title"],
"channel": meta["channel_name"], "channel": meta["channel_name"],
"published": meta["published"], "published": meta["published"],
"symlink": str(dest_file) "symlink": str(dest_file)
}) })
except Exception as e: except Exception as e:
conn.rollback() conn.rollback()
return str(e) return str(e)