takeone-youtube-clone/app/Console/Commands/CleanupOrphanedVideos.php

103 lines
3.4 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\Video;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
class CleanupOrphanedVideos extends Command
{
protected $signature = 'cleanup:orphaned-videos {--dry-run : List orphans without deleting (default)} {--force : Actually delete orphans}';
protected $description = 'Remove orphaned video files not linked to any database record';
public function handle()
{
$dryRun = $this->option('dry-run') !== false;
$force = $this->option('force');
if (!$dryRun && !$force) {
$this->warn('Use --dry-run (default) to preview, or --force to delete.');
return 1;
}
$this->info($dryRun ? 'DRY RUN MODE - No files will be deleted.' : 'FORCE MODE - Deleting orphaned files.');
$disk = Storage::disk('public');
$videoDir = 'videos';
$files = $disk->files($videoDir);
if (empty($files)) {
$this->info('No video files found in storage/app/public/videos/');
return 0;
}
// Get all valid filenames from DB (exact match for filename column)
$dbFilenames = Video::pluck('filename')->filter()->toArray();
$orphans = [];
foreach ($files as $file) {
// Only video files (skip non-videos)
if (!str_ends_with($file, '.mp4') && !str_ends_with($file, '.webm') && !str_ends_with($file, '.mov')) {
continue;
}
$basename = basename($file);
// Check if exact match or compressed_ prefix with base in DB
$isOrphan = true;
if (in_array($basename, $dbFilenames)) {
$isOrphan = false;
} elseif (str_starts_with($basename, 'compressed_')) {
$originalBasename = substr($basename, 10); // remove 'compressed_'
if (in_array($originalBasename, $dbFilenames)) {
$isOrphan = false;
}
}
if ($isOrphan) {
$orphans[] = $file;
}
}
$totalFiles = count($files);
$orphanCount = count($orphans);
$this->table(
['Stat', 'Value'],
[
['Total video files scanned', $totalFiles],
['Orphaned files found', $orphanCount],
]
);
if ($orphanCount === 0) {
$this->info('No orphaned videos found! ✅');
Log::channel('orphaned-videos')->info('Cleanup run: 0 orphans found.');
return 0;
}
if ($dryRun) {
$this->table(['Orphan Files (would delete)'], array_map(fn($f) => [ $f ], $orphans));
Log::channel('orphaned-videos')->info('DRY RUN: Found ' . $orphanCount . ' orphans', ['files' => $orphans]);
$this->warn("Run with --force to delete these files.");
} else {
$bar = $this->output->createProgressBar($orphanCount);
$bar->start();
foreach ($orphans as $orphan) {
$disk->delete($orphan);
Log::channel('orphaned-videos')->info('Deleted orphan: ' . $orphan);
$bar->advance();
}
$bar->finish();
$this->newLine();
$this->info("✅ Deleted {$orphanCount} orphaned video files.");
}
return 0;
}
}