203 lines
6 KiB
Go
203 lines
6 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"veza-backend-api/internal/models"
|
|
|
|
"go.uber.org/zap"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// HLSCleanupService gère le nettoyage des segments HLS obsolètes
|
|
type HLSCleanupService struct {
|
|
db *gorm.DB
|
|
outputDir string
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// NewHLSCleanupService crée un nouveau service de cleanup HLS
|
|
func NewHLSCleanupService(db *gorm.DB, outputDir string, logger *zap.Logger) *HLSCleanupService {
|
|
if logger == nil {
|
|
logger = zap.NewNop()
|
|
}
|
|
return &HLSCleanupService{
|
|
db: db,
|
|
outputDir: outputDir,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// CleanupDeletedTracks nettoie les segments HLS des tracks supprimés
|
|
// MIGRATION UUID: Completée. TrackID et StreamID en UUID.
|
|
func (s *HLSCleanupService) CleanupDeletedTracks(ctx context.Context) (int, error) {
|
|
var streams []models.HLSStream
|
|
if err := s.db.WithContext(ctx).Find(&streams).Error; err != nil {
|
|
return 0, fmt.Errorf("failed to fetch streams: %w", err)
|
|
}
|
|
|
|
cleanedCount := 0
|
|
for _, stream := range streams {
|
|
var track models.Track
|
|
if err := s.db.WithContext(ctx).First(&track, "id = ?", stream.TrackID).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
// Track deleted, cleanup segments
|
|
s.logger.Info("Cleaning up segments for deleted track",
|
|
zap.String("stream_id", stream.ID.String()),
|
|
zap.String("track_id", stream.TrackID.String()))
|
|
|
|
if err := s.cleanupStreamFiles(stream); err != nil {
|
|
s.logger.Error("Failed to cleanup stream files",
|
|
zap.String("stream_id", stream.ID.String()),
|
|
zap.Error(err))
|
|
// Continue avec les autres streams même en cas d'erreur
|
|
}
|
|
|
|
if err := s.db.WithContext(ctx).Delete(&stream).Error; err != nil {
|
|
s.logger.Error("Failed to delete stream record",
|
|
zap.String("stream_id", stream.ID.String()),
|
|
zap.Error(err))
|
|
// Continue avec les autres streams
|
|
} else {
|
|
cleanedCount++
|
|
}
|
|
} else {
|
|
s.logger.Error("Failed to check track existence",
|
|
zap.String("stream_id", stream.ID.String()),
|
|
zap.String("track_id", stream.TrackID.String()),
|
|
zap.Error(err))
|
|
}
|
|
}
|
|
}
|
|
|
|
s.logger.Info("Cleanup deleted tracks completed",
|
|
zap.Int("cleaned_count", cleanedCount))
|
|
return cleanedCount, nil
|
|
}
|
|
|
|
// CleanupOrphanedSegments nettoie les segments HLS qui n'ont pas de stream associé dans la base de données
|
|
func (s *HLSCleanupService) CleanupOrphanedSegments(ctx context.Context) (int, error) {
|
|
// Récupérer tous les streams valides
|
|
var streams []models.HLSStream
|
|
if err := s.db.WithContext(ctx).Find(&streams).Error; err != nil {
|
|
return 0, fmt.Errorf("failed to fetch streams: %w", err)
|
|
}
|
|
|
|
// Créer un map des répertoires de streams valides
|
|
validDirs := make(map[string]bool)
|
|
for _, stream := range streams {
|
|
// Construire le chemin du répertoire du stream
|
|
trackDir := filepath.Join(s.outputDir, fmt.Sprintf("track_%s", stream.TrackID))
|
|
validDirs[trackDir] = true
|
|
}
|
|
|
|
// Parcourir le répertoire de sortie HLS
|
|
cleanedCount := 0
|
|
err := filepath.Walk(s.outputDir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
// Ignorer les erreurs de lecture de répertoire
|
|
return nil
|
|
}
|
|
|
|
// Vérifier si c'est un répertoire de track (format: track_XXX)
|
|
if !info.IsDir() {
|
|
return nil
|
|
}
|
|
|
|
// Obtenir le répertoire parent pour vérifier si c'est un track_XXX
|
|
dir := path
|
|
base := filepath.Base(dir)
|
|
if !strings.HasPrefix(base, "track_") {
|
|
return nil
|
|
}
|
|
|
|
// Vérifier si ce répertoire est dans la liste des répertoires valides
|
|
if !validDirs[dir] {
|
|
s.logger.Info("Found orphaned segment directory",
|
|
zap.String("path", dir))
|
|
|
|
// Supprimer le répertoire orphelin
|
|
if err := os.RemoveAll(dir); err != nil {
|
|
s.logger.Error("Failed to remove orphaned directory",
|
|
zap.String("path", dir),
|
|
zap.Error(err))
|
|
return nil // Continue avec les autres répertoires
|
|
}
|
|
|
|
cleanedCount++
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return cleanedCount, fmt.Errorf("failed to walk output directory: %w", err)
|
|
}
|
|
|
|
s.logger.Info("Cleanup orphaned segments completed",
|
|
zap.Int("cleaned_count", cleanedCount))
|
|
return cleanedCount, nil
|
|
}
|
|
|
|
// cleanupStreamFiles supprime les fichiers d'un stream
|
|
func (s *HLSCleanupService) cleanupStreamFiles(stream models.HLSStream) error {
|
|
// Construire le chemin du répertoire du track
|
|
trackDir := filepath.Join(s.outputDir, fmt.Sprintf("track_%s", stream.TrackID))
|
|
|
|
// Vérifier que le chemin est sécurisé (pas de directory traversal)
|
|
absTrackDir, err := filepath.Abs(trackDir)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get absolute path: %w", err)
|
|
}
|
|
|
|
absOutputDir, err := filepath.Abs(s.outputDir)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get absolute output dir: %w", err)
|
|
}
|
|
|
|
// Vérifier que le répertoire est bien dans outputDir
|
|
if !strings.HasPrefix(absTrackDir, absOutputDir) {
|
|
return fmt.Errorf("invalid track directory path: %s", trackDir)
|
|
}
|
|
|
|
// Supprimer le répertoire et tous ses contenus
|
|
if err := os.RemoveAll(trackDir); err != nil {
|
|
return fmt.Errorf("failed to remove track directory: %w", err)
|
|
}
|
|
|
|
s.logger.Debug("Cleaned up stream files",
|
|
zap.String("track_id", stream.TrackID.String()),
|
|
zap.String("track_dir", trackDir))
|
|
|
|
return nil
|
|
}
|
|
|
|
// CleanupAll exécute tous les nettoyages
|
|
func (s *HLSCleanupService) CleanupAll(ctx context.Context) error {
|
|
s.logger.Info("Starting HLS cleanup")
|
|
|
|
// Nettoyer les tracks supprimés
|
|
deletedCount, err := s.CleanupDeletedTracks(ctx)
|
|
if err != nil {
|
|
s.logger.Error("Failed to cleanup deleted tracks", zap.Error(err))
|
|
return fmt.Errorf("failed to cleanup deleted tracks: %w", err)
|
|
}
|
|
|
|
// Nettoyer les segments orphelins
|
|
orphanedCount, err := s.CleanupOrphanedSegments(ctx)
|
|
if err != nil {
|
|
s.logger.Error("Failed to cleanup orphaned segments", zap.Error(err))
|
|
return fmt.Errorf("failed to cleanup orphaned segments: %w", err)
|
|
}
|
|
|
|
s.logger.Info("HLS cleanup completed",
|
|
zap.Int("deleted_tracks_cleaned", deletedCount),
|
|
zap.Int("orphaned_segments_cleaned", orphanedCount))
|
|
|
|
return nil
|
|
}
|