cacheDir = $cacheDir ?? (__DIR__ . '/../cache/api'); $this->enabled = true; // Créer le répertoire de cache s'il n'existe pas if (!is_dir($this->cacheDir)) { mkdir($this->cacheDir, 0755, true); } } /** * Génère une clé de cache */ private function getCacheKey($endpoint, $params = []) { $key = $endpoint; if (!empty($params)) { ksort($params); $key .= '_' . md5(serialize($params)); } return 'cache_' . md5($key) . '.json'; } /** * Récupère depuis le cache */ public function get($endpoint, $params = []) { if (!$this->enabled) return null; $file = $this->cacheDir . '/' . $this->getCacheKey($endpoint, $params); if (!file_exists($file)) { return null; } $data = json_decode(file_get_contents($file), true); // Vérifier l'expiration if (isset($data['expires']) && time() > $data['expires']) { unlink($file); return null; } return $data['content'] ?? null; } /** * Stocke dans le cache */ public function set($endpoint, $params, $content, $ttl = 300) { if (!$this->enabled) return; $file = $this->cacheDir . '/' . $this->getCacheKey($endpoint, $params); $data = [ 'content' => $content, 'expires' => time() + $ttl, 'created' => time() ]; // Verrou exclusif : évite les écritures concurrentes tronquées file_put_contents($file, json_encode($data), LOCK_EX); } /** * Vide entièrement le cache (entrées valides comme expirées) * * @return int Nombre de fichiers supprimés */ public function clear() { $files = glob($this->cacheDir . '/cache_*.json'); $deleted = 0; foreach ($files as $file) { if (unlink($file)) { $deleted++; } } return $deleted; } /** * Nettoie le cache expiré */ public function cleanup() { $files = glob($this->cacheDir . '/cache_*.json'); $cleaned = 0; foreach ($files as $file) { $data = json_decode(file_get_contents($file), true); if (isset($data['expires']) && time() > $data['expires']) { unlink($file); $cleaned++; } } return $cleaned; } } // Instance globale $GLOBALS['simple_api_cache'] = new SimpleAPICache(); /** * TTL de cache selon l'endpoint PeerTube. * * Correspondance exacte d'abord, puis par préfixe. L'ancien matching par * sous-chaîne (strpos) créait des collisions : « accounts/{nom}/videos » * capturait le TTL de « videos » (10 min) au lieu de celui de « accounts » * (5 min, pensé pour les lives). * * @param string $endpoint Endpoint de l'API (sans slash initial) * @return int TTL en secondes */ function getPeerTubeCacheTtl($endpoint) { static $exactMap = [ 'videos/categories' => 3600, // 1 heure 'videos' => 600, // 10 minutes 'search/videos' => 600, // 10 minutes 'wp-posts' => 900, // 15 minutes pour WordPress 'accounts' => 300 // 5 minutes pour live streams ]; static $prefixMap = [ 'accounts/' => 300, // lives : accounts/{nom}/videos 'video-channels/' => 600, // vidéos d'une chaîne 'videos/' => 600 // videos/{id}, commentaires ]; if (isset($exactMap[$endpoint])) { return $exactMap[$endpoint]; } foreach ($prefixMap as $prefix => $ttl) { if (strpos($endpoint, $prefix) === 0) { return $ttl; } } return 300; // Défaut 5 minutes } /** * Version cachée de callPeerTubeApi - remplace l'originale */ function callPeerTubeApiCached($endpoint, $params = []) { // Cache désactivé : appel direct, sans lecture ni écriture if (!defined('CACHE_ENABLED') || !CACHE_ENABLED) { return callPeerTubeApiOriginal($endpoint, $params); } $cache = $GLOBALS['simple_api_cache']; $ttl = getPeerTubeCacheTtl($endpoint); // Essayer le cache d'abord $cached = $cache->get($endpoint, $params); if ($cached !== null) { return $cached; } // Appeler l'API originale $data = callPeerTubeApiOriginal($endpoint, $params); // Mettre en cache si on a des données if (!empty($data)) { $cache->set($endpoint, $params, $data, $ttl); } return $data; } /** * Cache générique pour tout type d'API */ function callApiCached($cacheKey, $callable, $ttl = 900) { $cache = $GLOBALS['simple_api_cache']; // Essayer le cache d'abord $cached = $cache->get($cacheKey, []); if ($cached !== null) { return $cached; } // Appeler la fonction fournie try { $data = call_user_func($callable); } catch (Exception $e) { error_log('API call failed for ' . $cacheKey . ': ' . $e->getMessage()); return []; } // Mettre en cache si on a des données if (!empty($data)) { $cache->set($cacheKey, [], $data, $ttl); } return $data; } /** * Helpers simples pour le cache générique */ function getFromCache($key) { if (!defined('CACHE_ENABLED') || !CACHE_ENABLED) { return null; } $cache = $GLOBALS['simple_api_cache']; return $cache->get($key, []); } function saveToCache($key, $data, $ttl = null) { if (!defined('CACHE_ENABLED') || !CACHE_ENABLED) { return; } $ttl = $ttl ?? (defined('CACHE_DURATION') ? CACHE_DURATION : 3600); $cache = $GLOBALS['simple_api_cache']; $cache->set($key, [], $data, $ttl); } // Nettoyage automatique occasionnel if (rand(1, 100) === 1) { $GLOBALS['simple_api_cache']->cleanup(); } ?>