option('from'); $toDisk = (string) ($this->option('to') ?: config('transfer.files_disk', 'transfer-files')); $dryRun = (bool) $this->option('dry-run'); $deleteSource = (bool) $this->option('delete-source'); if ($fromDisk === $toDisk) { $this->error('Source and target disks must differ.'); return self::FAILURE; } if (! config("filesystems.disks.{$fromDisk}") || ! config("filesystems.disks.{$toDisk}")) { $this->error('Unknown filesystem disk configured for migration.'); return self::FAILURE; } $files = TransferFile::query() ->where('disk', $fromDisk) ->orderBy('id') ->get(); if ($files->isEmpty()) { $this->info("No transfer files on disk [{$fromDisk}]."); return self::SUCCESS; } $this->info(sprintf( '%s migrating %d file(s) from [%s] to [%s].', $dryRun ? 'Would migrate' : 'Migrating', $files->count(), $fromDisk, $toDisk, )); $migrated = 0; $skipped = 0; $failed = 0; foreach ($files as $file) { $label = "#{$file->id} {$file->path}"; if (! Storage::disk($fromDisk)->exists($file->path)) { $this->warn("Missing source blob: {$label}"); $failed++; continue; } if (Storage::disk($toDisk)->exists($file->path)) { if (! $dryRun) { $file->update(['disk' => $toDisk]); if ($deleteSource) { Storage::disk($fromDisk)->delete($file->path); } } $this->line("Already on target, updated record: {$label}"); $skipped++; continue; } if ($dryRun) { $this->line("Would migrate: {$label}"); $migrated++; continue; } try { $stream = Storage::disk($fromDisk)->readStream($file->path); if ($stream === false) { throw new \RuntimeException('Could not open source stream.'); } $written = Storage::disk($toDisk)->writeStream($file->path, $stream); if (is_resource($stream)) { fclose($stream); } if ($written === false) { throw new \RuntimeException('Upload to target disk failed.'); } if (! Storage::disk($toDisk)->exists($file->path)) { throw new \RuntimeException('Uploaded blob not found on target disk.'); } $file->update(['disk' => $toDisk]); if ($deleteSource) { Storage::disk($fromDisk)->delete($file->path); } $this->line("Migrated: {$label}"); $migrated++; } catch (\Throwable $e) { $this->error("Failed {$label}: {$e->getMessage()}"); $failed++; } } $this->newLine(); $this->info("Done. migrated={$migrated} skipped={$skipped} failed={$failed}"); return $failed > 0 ? self::FAILURE : self::SUCCESS; } }