fix: prevent orphaned folder creation and improve cleanup
All checks were successful
Docker Build / build (push) Successful in 13s
All checks were successful
Docker Build / build (push) Successful in 13s
This commit is contained in:
parent
8a9f8fbb35
commit
dd25df4bdc
1 changed files with 101 additions and 120 deletions
213
ta_symlink.py
213
ta_symlink.py
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue