pdo = $pdo; } public function index(Request $request, Response $response, $args) { $sourceModel = new Source($this->pdo); $sources = $sourceModel->findAll(); $syncLogModel = new SyncLog($this->pdo); $recentSyncs = SyncLog::getRecent($this->pdo, 10); return $this->view->render($response, 'admin/index.twig', [ 'title' => 'Admin Dashboard', 'sources' => $sources, 'recent_syncs' => $recentSyncs ]); } public function syncSource(Request $request, Response $response, $args) { $sourceId = $args['id']; $syncType = $request->getQueryParams()['type'] ?? 'full'; $sourceModel = new Source($this->pdo); $source = $sourceModel->find($sourceId); if (!$source) { return $response->withStatus(404)->withHeader('Content-Type', 'application/json'); } // Validate sync type based on source type if ($source['name'] === 'jellyfin') { $validSyncTypes = ['full', 'incremental', 'all', 'movies', 'tvshows', 'cleanup']; if (!in_array($syncType, $validSyncTypes)) { return $this->json($response, [ 'success' => false, 'message' => 'Invalid sync type for Jellyfin source. Valid types: ' . implode(', ', $validSyncTypes) ], 400); } } else { // For other sources, only allow full/incremental $validSyncTypes = ['full', 'incremental']; if (!in_array($syncType, $validSyncTypes)) { return $this->json($response, [ 'success' => false, 'message' => 'Invalid sync type. Valid types: ' . implode(', ', $validSyncTypes) ], 400); } } // Start sync in background (simplified - in production you'd use queues) $syncLogId = $this->startSync($source, $syncType); return $this->json($response, [ 'success' => true, 'sync_log_id' => $syncLogId, 'message' => 'Sync started successfully' ]); } public function syncStatus(Request $request, Response $response, $args) { $syncLogId = $args['id']; $syncLogModel = new SyncLog($this->pdo); $syncLog = $syncLogModel->find($syncLogId); if (!$syncLog) { return $response->withStatus(404)->withHeader('Content-Type', 'application/json'); } return $this->json($response, [ 'id' => $syncLog['id'], 'status' => $syncLog['status'], 'sync_type' => $syncLog['sync_type'], 'total_items' => $syncLog['total_items'] ?? 0, 'processed_items' => $syncLog['processed_items'], 'new_items' => $syncLog['new_items'], 'updated_items' => $syncLog['updated_items'], 'deleted_items' => $syncLog['deleted_items'], 'started_at' => $syncLog['started_at'], 'completed_at' => $syncLog['completed_at'], 'message' => $syncLog['message'], 'errors' => $syncLog['errors'] ? json_decode($syncLog['errors'], true) : [], 'progress_percentage' => $this->calculateProgressPercentage($syncLog) ]); } private function calculateProgressPercentage(array $syncLog): float { $total = $syncLog['total_items'] ?? 0; if ($total <= 0) return 0; $processed = $syncLog['processed_items'] ?? 0; return min(100, round(($processed / $total) * 100, 2)); } public function sources(Request $request, Response $response, $args) { $sourceModel = new Source($this->pdo); $sources = $sourceModel->findAll(); return $this->view->render($response, 'admin/sources.twig', [ 'title' => 'Source Management', 'sources' => $sources ]); } private function startSync(array $source, string $syncType): int { // Create sync log entry first $syncLogId = $this->createSyncLog($source, $syncType); // Start sync in background process $this->startBackgroundSync($source['id'], $syncType, $syncLogId); return $syncLogId; } private function createSyncLog(array $source, string $syncType): int { $data = [ 'source_id' => $source['id'], 'sync_type' => $syncType, 'status' => 'started', 'total_items' => 0, 'processed_items' => 0, 'new_items' => 0, 'updated_items' => 0, 'deleted_items' => 0, 'started_at' => date('Y-m-d H:i:s') ]; $columns = array_keys($data); $placeholders = array_map(fn($col) => ":$col", $columns); $sql = "INSERT INTO sync_logs (" . implode(', ', $columns) . ") VALUES (" . implode(', ', $placeholders) . ")"; $stmt = $this->pdo->prepare($sql); $stmt->execute($data); return (int) $this->pdo->lastInsertId(); } private function startBackgroundSync(int $sourceId, string $syncType, int $syncLogId): void { $scriptPath = __DIR__ . '/../../sync-runner.php'; $command = sprintf( 'php %s %d %s %d > /dev/null 2>&1 &', escapeshellarg($scriptPath), $sourceId, escapeshellarg($syncType), $syncLogId ); // Execute the command in background if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { // Windows pclose(popen('start /B ' . $command, 'r')); } else { // Unix-like systems exec($command); } // */ // Update sync log to indicate it's running (this will be updated again by the script) $syncLogModel = new SyncLog($this->pdo); $syncLogModel->update($syncLogId, [ 'status' => 'running', 'message' => 'Sync process starting in background' ]); } }