httpClient = new Client([ 'timeout' => 30, 'headers' => [ 'User-Agent' => 'MediaCollector/1.0', 'X-API-Key' => $source['api_key'] ] ]); $this->apiKey = $source['api_key']; $this->baseUrl = rtrim($source['api_url'], '/'); $this->imageDownloader = new ImageDownloader(); } protected function executeSync(string $syncType): void { if (empty($this->apiKey) || empty($this->baseUrl)) { throw new Exception('XBVR API key and URL not configured'); } $this->logProgress('Starting XBVR library sync...'); // Sync VR scenes $this->syncScenes(); $this->logProgress("Processed {$this->processedCount} XBVR items"); } private function syncScenes(): void { try { $scenes = $this->getXbvrScenes(); foreach ($scenes as $sceneData) { $this->syncScene($sceneData); $this->processedCount++; } } catch (Exception $e) { $this->logProgress('Error syncing XBVR scenes: ' . $e->getMessage()); } } private function getXbvrScenes(): array { try { // XBVR API endpoint for scenes $response = $this->httpClient->get("{$this->baseUrl}/api/scene"); $data = json_decode($response->getBody(), true); if (!isset($data['scenes'])) { throw new Exception('No scenes found in XBVR'); } return $data['scenes']; } catch (Exception $e) { throw new Exception('Failed to fetch XBVR scenes: ' . $e->getMessage()); } } private function syncScene(array $sceneData): void { $adultVideoModel = new AdultVideo($this->pdo); // Check if scene already exists by xbvr_id in metadata $stmt = $this->pdo->prepare(" SELECT id, metadata FROM adult_videos WHERE source_id = :source_id "); $stmt->execute(['source_id' => $this->source['id']]); $existingScenes = $stmt->fetchAll(\PDO::FETCH_ASSOC); $existingScene = null; foreach ($existingScenes as $scene) { $metadata = json_decode($scene['metadata'], true); if (isset($metadata['xbvr_id']) && $metadata['xbvr_id'] === $sceneData['id']) { $existingScene = $scene; break; } } private function syncScene(array $sceneData): void { $adultVideoModel = new AdultVideo($this->pdo); // Check if scene already exists by xbvr_id in metadata $stmt = $this->pdo->prepare(" SELECT id, metadata FROM adult_videos WHERE source_id = :source_id "); $stmt->execute(['source_id' => $this->source['id']]); $existingScenes = $stmt->fetchAll(\PDO::FETCH_ASSOC); $existingScene = null; foreach ($existingScenes as $scene) { $metadata = json_decode($scene['metadata'], true); if (isset($metadata['xbvr_id']) && $metadata['xbvr_id'] === $sceneData['id']) { $existingScene = $scene; break; } } // Download images locally $coverFilename = null; $screenshotFilename = null; if (!empty($sceneData['cover_url'])) { $coverFilename = $this->imageDownloader->generateFilename($sceneData['cover_url'], 'cover'); $localCoverPath = $this->imageDownloader->downloadImage($sceneData['cover_url'], $coverFilename, 'adult_videos'); if ($localCoverPath) { $sceneData['local_cover_path'] = $this->imageDownloader->getPublicUrl($localCoverPath); } } if (!empty($sceneData['screenshot_url'])) { $screenshotFilename = $this->imageDownloader->generateFilename($sceneData['screenshot_url'], 'screenshot'); $localScreenshotPath = $this->imageDownloader->downloadImage($sceneData['screenshot_url'], $screenshotFilename, 'adult_videos'); if ($localScreenshotPath) { $sceneData['local_screenshot_path'] = $this->imageDownloader->getPublicUrl($localScreenshotPath); } } // Handle actors $actors = $this->syncActors($sceneData['cast'] ?? []); $sceneData = [ 'title' => $sceneData['title'] ?: 'Untitled VR Scene', 'overview' => $sceneData['synopsis'] ?? null, 'release_date' => $sceneData['release_date'] ? date('Y-m-d', strtotime($sceneData['release_date'])) : null, 'runtime_minutes' => $sceneData['duration'] ?? null, 'rating' => $sceneData['rating'] ?? null, 'source_id' => $this->source['id'], 'external_id' => $sceneData['id'], 'metadata' => json_encode([ 'xbvr_id' => $sceneData['id'], 'xbvr_url' => $sceneData['scene_url'] ?? null, 'cast' => $sceneData['cast'] ?? [], 'actors' => $actors, 'tags' => $sceneData['tags'] ?? [], 'is_available' => $sceneData['is_available'] ?? true, 'is_watched' => $sceneData['is_watched'] ?? false, 'watch_count' => $sceneData['watch_count'] ?? 0, 'video_length' => $sceneData['video_length'] ?? null, 'video_width' => $sceneData['video_width'] ?? null, 'video_height' => $sceneData['video_height'] ?? null, 'video_codec' => $sceneData['video_codec'] ?? null, 'file_path' => $sceneData['file_path'] ?? null, 'cover_url' => $sceneData['cover_url'] ?? null, 'local_cover_path' => $sceneData['local_cover_path'] ?? null, 'screenshot_url' => $sceneData['screenshot_url'] ?? null, 'local_screenshot_path' => $sceneData['local_screenshot_path'] ?? null ]) ]; if ($existingScene) { $adultVideoModel->update($existingScene['id'], $sceneData); $this->updatedCount++; } else { $adultVideoModel->create($sceneData); $this->newCount++; } } private function syncActors(array $cast): array { $actors = []; foreach ($cast as $actorName) { if (empty($actorName)) continue; $actor = $this->getOrCreateActor($actorName); if ($actor) { $actors[] = $actor; } } return $actors; } private function getOrCreateActor(string $name): ?array { // Check if actor already exists $stmt = $this->pdo->prepare(" SELECT id, name, thumbnail_path FROM actors WHERE name = :name "); $stmt->execute(['name' => $name]); $existingActor = $stmt->fetch(\PDO::FETCH_ASSOC); if ($existingActor) { return [ 'id' => $existingActor['id'], 'name' => $existingActor['name'], 'thumbnail_path' => $existingActor['thumbnail_path'] ]; } // For now, we'll create actor without thumbnail // In a full implementation, you'd fetch actor details from XBVR API try { $stmt = $this->pdo->prepare(" INSERT INTO actors (name, created_at, updated_at) VALUES (:name, NOW(), NOW()) "); $stmt->execute(['name' => $name]); $actorId = $this->pdo->lastInsertId(); return [ 'id' => $actorId, 'name' => $name, 'thumbnail_path' => null ]; } catch (Exception $e) { $this->logProgress("Failed to create actor {$name}: " . $e->getMessage()); return null; } } protected function getProcessedCount(): int { return $this->processedCount; } protected function getNewCount(): int { return $this->newCount; } protected function getUpdatedCount(): int { return $this->updatedCount; } protected function getDeletedCount(): int { return 0; // XBVR doesn't provide deletion info in this context } }