import 'dart:io'; import 'dart:convert'; import 'package:shelf/shelf.dart'; import 'package:shelf_router/shelf_router.dart'; import '../database/database.dart'; import '../services/recording_scheduler.dart'; class RecordingsApi { final AppDatabase _db; final RecordingScheduler _scheduler; RecordingsApi(this._db, this._scheduler); /// Handler pour GET /api/recordings/logs/ /// Exposé séparément car shelf_router a un conflit entre DELETE / et GET /logs/ Future getLogHandler(Request request, String id) async { final recording = _db.getRecordingById(id); if (recording == null) { return Response.notFound( json.encode({'error': 'Enregistrement non trouvé'}), headers: {'Content-Type': 'application/json'}, ); } if (recording.filePath == null) { return Response.notFound( json.encode({'error': 'Aucun fichier ni log associé pour le moment.'}), headers: {'Content-Type': 'application/json'}, ); } final logFilePath = recording.filePath!.replaceAll('.mp4', '.log'); final logFile = File(logFilePath); if (!await logFile.exists()) { return Response.notFound( json.encode({'error': 'Le fichier de log est introuvable. Chemin: $logFilePath'}), headers: {'Content-Type': 'application/json'}, ); } final logs = await logFile.readAsString(); return Response.ok( json.encode({'logs': logs}), headers: {'Content-Type': 'application/json'}, ); } /// GET /api/recordings — Liste tous les enregistrements Response handleGetAll(Request request) { final recordings = _db.getAllRecordings(); return Response.ok( json.encode(recordings.map((r) => r.toMap()).toList()), headers: {'Content-Type': 'application/json'}, ); } /// POST /api/recordings — Planifie un nouvel enregistrement Future handlePost(Request request) async { try { final payload = await request.readAsString(); final data = json.decode(payload); final recording = _db.createRecording( userId: 'dev_user_id', channelId: data['channel_id'], streamUrl: data['stream_url'], title: data['title'] ?? 'Sans Titre', startTime: DateTime.parse(data['start_time']), endTime: DateTime.parse(data['end_time']), ); return Response.ok( recording.toJson(), headers: {'Content-Type': 'application/json'}, ); } catch (e) { return Response.internalServerError( body: json.encode({'error': 'Erreur lors de la programmation: $e'}), headers: {'Content-Type': 'application/json'}, ); } } /// DELETE /api/recordings/ — Annule ou supprime un enregistrement /// Si un enregistrement FFmpeg est actif, il est arrêté avant la suppression Future handleDelete(Request request, String id) async { final recording = _db.getRecordingById(id); if (recording == null) { return Response.notFound( json.encode({'error': 'Enregistrement non trouvé'}), headers: {'Content-Type': 'application/json'}, ); } // Tuer FFmpeg si cet enregistrement est en cours AVANT de supprimer de la DB await _scheduler.stopRecording(id); _db.deleteRecording(id); return Response.ok( json.encode({'message': 'Enregistrement supprimé avec succès'}), headers: {'Content-Type': 'application/json'}, ); } /// POST /api/recordings/stop/ — Arrête un enregistrement FFmpeg en cours Future handleStop(Request request, String id) async { final recording = _db.getRecordingById(id); if (recording == null) { return Response.notFound( json.encode({'error': 'Enregistrement non trouvé'}), headers: {'Content-Type': 'application/json'}, ); } final stopped = await _scheduler.stopRecording(id); if (stopped) { return Response.ok( json.encode({'message': 'Enregistrement arrêté'}), headers: {'Content-Type': 'application/json'}, ); } else { // Pas de processus FFmpeg actif pour cet ID → marquer comme complété quand même _db.updateRecordingStatus(id, 'completed'); return Response.ok( json.encode({'message': 'Enregistrement marqué comme terminé'}), headers: {'Content-Type': 'application/json'}, ); } } }