0, 'total' => 0, 'phase' => 'Counting files...', 'done' => false, 'error' => null, ]; $this->updateProgress($progress); try { // ── Count all items ─────────────────────────────────────────────── $videos = Video::where('path', 'like', 'users/%')->get(); $slides = \DB::table('video_slides') ->where('filename', 'like', 'users/%') ->get(); $usersWithAvatar = User::where('avatar', 'like', 'users/%')->get(); $usersWithBanner = User::where('banner', 'like', 'users/%')->get(); $postImages = \DB::table('post_images') ->where('filename', 'like', 'users/%') ->get(); // Count thumbnails separately (they're additional per-video downloads) $videoThumbs = $videos->filter(fn($v) => $v->thumbnail && str_starts_with($v->thumbnail, 'users/')); $total = $videos->count() + $videoThumbs->count() + $slides->count() + $usersWithAvatar->count() + $usersWithBanner->count() + $postImages->count(); $progress['total'] = $total; $progress['phase'] = 'Downloading files from NAS...'; $this->updateProgress($progress); // ── Download videos ─────────────────────────────────────────────── foreach ($videos as $video) { $nasPath = $video->path; $localPath = storage_path('app/' . $nasPath); try { $nas->ensureLocalAsset($localPath, $nasPath); } catch (\Throwable $e) { Log::error('NasToLocalMigrationJob: failed to download video #' . $video->id . ' path=' . $nasPath . ': ' . $e->getMessage()); } $progress['current']++; $progress['phase'] = 'Downloading videos... (' . $progress['current'] . '/' . $total . ')'; $this->updateProgress($progress); } // ── Download thumbnails ─────────────────────────────────────────── foreach ($videoThumbs as $video) { $nasPath = $video->thumbnail; $localPath = storage_path('app/' . $nasPath); try { $nas->ensureLocalAsset($localPath, $nasPath); } catch (\Throwable $e) { Log::error('NasToLocalMigrationJob: failed to download thumbnail for video #' . $video->id . ' path=' . $nasPath . ': ' . $e->getMessage()); } $progress['current']++; $progress['phase'] = 'Downloading thumbnails... (' . $progress['current'] . '/' . $total . ')'; $this->updateProgress($progress); } // ── Download slides ─────────────────────────────────────────────── foreach ($slides as $slide) { $nasPath = $slide->filename; $localPath = storage_path('app/' . $nasPath); try { $nas->ensureLocalAsset($localPath, $nasPath); } catch (\Throwable $e) { Log::error('NasToLocalMigrationJob: failed to download slide id=' . $slide->id . ' path=' . $nasPath . ': ' . $e->getMessage()); } $progress['current']++; $progress['phase'] = 'Downloading audio slides... (' . $progress['current'] . '/' . $total . ')'; $this->updateProgress($progress); } // ── Download user avatars ───────────────────────────────────────── foreach ($usersWithAvatar as $user) { $nasPath = $user->avatar; $localPath = storage_path('app/' . $nasPath); try { $nas->ensureLocalAsset($localPath, $nasPath); } catch (\Throwable $e) { Log::error('NasToLocalMigrationJob: failed to download avatar for user #' . $user->id . ' path=' . $nasPath . ': ' . $e->getMessage()); } $progress['current']++; $progress['phase'] = 'Downloading avatars... (' . $progress['current'] . '/' . $total . ')'; $this->updateProgress($progress); } // ── Download user banners ───────────────────────────────────────── foreach ($usersWithBanner as $user) { $nasPath = $user->banner; $localPath = storage_path('app/' . $nasPath); try { $nas->ensureLocalAsset($localPath, $nasPath); } catch (\Throwable $e) { Log::error('NasToLocalMigrationJob: failed to download banner for user #' . $user->id . ' path=' . $nasPath . ': ' . $e->getMessage()); } $progress['current']++; $progress['phase'] = 'Downloading banners... (' . $progress['current'] . '/' . $total . ')'; $this->updateProgress($progress); } // ── Download post images ────────────────────────────────────────── foreach ($postImages as $img) { $nasPath = $img->filename; $localPath = storage_path('app/' . $nasPath); try { $nas->ensureLocalAsset($localPath, $nasPath); } catch (\Throwable $e) { Log::error('NasToLocalMigrationJob: failed to download post image id=' . $img->id . ' path=' . $nasPath . ': ' . $e->getMessage()); } $progress['current']++; $progress['phase'] = 'Downloading post images... (' . $progress['current'] . '/' . $total . ')'; $this->updateProgress($progress); } // ── Disable NAS ─────────────────────────────────────────────────── Setting::set('nas_sync_enabled', 'false'); app(\App\Services\NasSyncService::class)->flushReachabilityCache(); $progress['done'] = true; $progress['phase'] = 'Complete'; $this->updateProgress($progress); Log::info('NasToLocalMigrationJob: completed. ' . $total . ' files migrated. NAS disabled.'); } catch (\Throwable $e) { Log::error('NasToLocalMigrationJob: fatal error: ' . $e->getMessage(), [ 'trace' => $e->getTraceAsString(), ]); $progress['error'] = $e->getMessage(); $this->updateProgress($progress); } } }