import 'dart:convert'; import 'package:shared_preferences/shared_preferences.dart'; import '../utils/logger.dart'; /// Service de cache stratégique avec TTL (Time To Live) /// pour optimiser les performances et réduire les appels API class CacheService { final SharedPreferences _prefs; CacheService(this._prefs); /// Clés de cache avec leur TTL en secondes static const Map _cacheTTL = { 'dashboard_stats': 300, // 5 minutes 'parametres_lcb_ft': 1800, // 30 minutes 'user_profile': 600, // 10 minutes 'organisations': 3600, // 1 heure 'notifications_count': 60, // 1 minute }; /// Met en cache une valeur avec un TTL automatique selon la clé Future set(String key, dynamic value) async { try { final cacheData = { 'value': value, 'timestamp': DateTime.now().millisecondsSinceEpoch, }; final jsonString = json.encode(cacheData); final success = await _prefs.setString(key, jsonString); if (success) { AppLogger.debug('Cache set: $key (TTL: ${_getTTL(key)}s)'); } return success; } catch (e) { AppLogger.error('Erreur lors de la mise en cache de $key', e); return false; } } /// Récupère une valeur depuis le cache si elle n'est pas expirée /// Retourne null si la clé n'existe pas ou si le cache est expiré T? get(String key) { try { final jsonString = _prefs.getString(key); if (jsonString == null) { return null; } final cacheData = json.decode(jsonString) as Map; final timestamp = cacheData['timestamp'] as int; final now = DateTime.now().millisecondsSinceEpoch; // Vérifier si le cache a expiré final ttl = _getTTL(key) * 1000; // Convertir en millisecondes if (now - timestamp > ttl) { AppLogger.debug('Cache expiré: $key'); remove(key); // Nettoyer return null; } final value = cacheData['value']; AppLogger.debug('Cache hit: $key'); return value as T; } catch (e) { AppLogger.error('Erreur lors de la lecture du cache $key', e); return null; } } /// Récupère une valeur String depuis le cache String? getString(String key) => get(key); /// Récupère une valeur Map depuis le cache Map? getMap(String key) { final value = get>(key); if (value == null) return null; return Map.from(value); } /// Récupère une valeur List depuis le cache List? getList(String key) { final value = get>(key); if (value == null) return null; return List.from(value); } /// Supprime une clé du cache Future remove(String key) async { try { return await _prefs.remove(key); } catch (e) { AppLogger.error('Erreur lors de la suppression du cache $key', e); return false; } } /// Nettoie toutes les clés d'un préfixe donné Future clearByPrefix(String prefix) async { try { final keys = _prefs.getKeys(); final keysToRemove = keys.where((k) => k.startsWith(prefix)); for (final key in keysToRemove) { await remove(key); } AppLogger.info('Cache nettoyé pour préfixe: $prefix'); } catch (e) { AppLogger.error('Erreur lors du nettoyage du cache $prefix', e); } } /// Nettoie tout le cache Future clearAll() async { try { return await _prefs.clear(); } catch (e) { AppLogger.error('Erreur lors du nettoyage complet du cache', e); return false; } } /// Nettoie les caches expirés (maintenance) Future cleanupExpired() async { try { final keys = _prefs.getKeys(); int cleaned = 0; for (final key in keys) { final jsonString = _prefs.getString(key); if (jsonString == null) continue; try { final cacheData = json.decode(jsonString) as Map; final timestamp = cacheData['timestamp'] as int; final now = DateTime.now().millisecondsSinceEpoch; final ttl = _getTTL(key) * 1000; if (now - timestamp > ttl) { await remove(key); cleaned++; } } catch (_) { // Données corrompues, supprimer await remove(key); cleaned++; } } if (cleaned > 0) { AppLogger.info('$cleaned entrées de cache expirées nettoyées'); } } catch (e) { AppLogger.error('Erreur lors du nettoyage des caches expirés', e); } } /// Récupère le TTL d'une clé en secondes int _getTTL(String key) { // Chercher une correspondance exacte if (_cacheTTL.containsKey(key)) { return _cacheTTL[key]!; } // Chercher par préfixe for (final entry in _cacheTTL.entries) { if (key.startsWith(entry.key)) { return entry.value; } } // TTL par défaut : 5 minutes return 300; } /// Vérifie si une clé existe et n'est pas expirée bool has(String key) { return get(key) != null; } /// Retourne des statistiques sur le cache Map getStats() { final keys = _prefs.getKeys(); int total = keys.length; int expired = 0; int valid = 0; for (final key in keys) { if (get(key) == null) { expired++; } else { valid++; } } return { 'total': total, 'valid': valid, 'expired': expired, }; } }