package controllers import ( "canguidev/shelfy/internal/client" runner "canguidev/shelfy/internal/job" "canguidev/shelfy/internal/models" "context" "log" "net/http" "os" "path/filepath" "regexp" "strconv" "strings" "sync" "github.com/gin-gonic/gin" "gorm.io/gorm" ) func sanitizeFileName(name string) string { return runner.SanitizeFileName(name) } var seriesRegex = regexp.MustCompile(`^(.+?)\.S\d{2}E\d{2}`) var ( jobs = make(map[string]*runner.DownloadJob) jobsMu sync.Mutex ) func HandleAddJobsMultiple(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { // 1. Parsing des champs du formulaire var form struct { Links string `json:"links" form:"links" binding:"required"` PathID string `json:"path_id" form:"path_id" binding:"required"` } // On tente d'abord le bind JSON if err := c.ShouldBindJSON(&form); err != nil { log.Printf("[DEBUG] Reçu links=%q | path_id=%q", form.Links, form.PathID) // Si JSON échoue, on tente avec form-urlencoded if err := c.ShouldBind(&form); err != nil { log.Printf("[DEBUG] Reçu links=%q | path_id=%q", form.Links, form.PathID) c.JSON(http.StatusBadRequest, gin.H{"error": "Champs requis manquants ou invalides"}) return } } lines := strings.Split(form.Links, "\n") baseID, err := strconv.ParseInt(form.PathID, 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "ID de chemin invalide"}) return } // 2. Vérification du dossier principal var paths []models.PathDownload if err := db.Find(&paths).Error; err != nil { log.Printf("[DEBUG] Erreur lors du Find: %v", err) } else { log.Printf("[DEBUG] Contenu de la table PathDownload avant recherche: %+v", paths) } var basePath models.PathDownload if err := db.First(&basePath, baseID).Error; err != nil { log.Printf("[DEBUG] Pas trouvé baseID=%v dans PathDownload", baseID) c.JSON(http.StatusBadRequest, gin.H{"error": "Dossier principal introuvable"}) return } log.Printf("[DEBUG] PathDownload trouvé pour baseID=%v: %+v", baseID, basePath) // 3. Initialisation Debrid-Link ctx := context.Background() clt := client.NewClient(db) account := runner.GetFirstActiveAccount(clt) if account == nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Aucun compte Debrid-Link actif"}) return } clt.SetAccount(account) // 4. Résultats à retourner var results []map[string]interface{} // 5. Itération des liens for _, link := range lines { link = strings.TrimSpace(link) if link == "" { continue } links, err := clt.AddLink(ctx, link) if err != nil { log.Printf("Échec débridage de %s: %v", link, err) results = append(results, gin.H{"link": link, "status": "failed", "error": err.Error()}) continue } for _, l := range links { clean := sanitizeFileName(l.Name) series := clean if m := seriesRegex.FindStringSubmatch(clean); len(m) == 2 { series = m[1] } assignID := int(basePath.ID) if series != "" { dirPath := filepath.Join(basePath.Path, series) if err := os.MkdirAll(dirPath, os.ModePerm); err != nil { log.Printf("Erreur création dossier %s: %v", dirPath, err) } var sub models.PathDownload if err := db.Where("path = ?", dirPath).First(&sub).Error; err != nil { if err == gorm.ErrRecordNotFound { sub = models.PathDownload{Path: dirPath, PathName: series} if err := db.Create(&sub).Error; err != nil { log.Printf("Erreur création PathDownload: %v", err) } } else { log.Printf("Erreur lecture PathDownload: %v", err) } } assignID = int(sub.ID) } // streamInfo, err := clt.CreateTranscode(ctx, l.ID) // if err != nil { // log.Printf("Erreur transcode pour %s: %v", l.ID, err) // } job := &runner.DownloadJob{ ID: l.ID, Link: l.DownloadURL, Name: l.Name, Status: "waiting", PathID: assignID, Size: l.Size, Host: l.Host, Progress: 0, StreamURL: "", } // if streamInfo != nil { // job.StreamURL = streamInfo.StreamURL // } if err := runner.RegisterJobWithDB(job, db); err != nil { log.Printf("Erreur enregistrement job: %v", err) results = append(results, gin.H{"link": l.URL, "status": "failed", "error": err.Error()}) continue } results = append(results, gin.H{"link": l.URL, "status": "added", "name": job.Name}) } } // 6. Broadcast au frontend runner.Broadcast() c.JSON(http.StatusOK, gin.H{ "message": "Traitement terminé", "results": results, }) } } func HandleStartJob(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { id := c.Param("id") log.Printf("[StartJob] ID = %s", id) // 1. Vérifie si le job est déjà en mémoire jobsMu.Lock() job, exists := jobs[id] jobsMu.Unlock() // 2. Sinon, récupère depuis la BDD if !exists { var j runner.DownloadJob if err := db.First(&j, "id = ?", id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Job introuvable"}) return } jobCopy := j jobsMu.Lock() jobs[id] = &jobCopy job = &jobCopy jobsMu.Unlock() } clt:= client.NewClient(db) account := runner.GetFirstActiveAccount(clt) if account == nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Aucun compte Debrid-Link actif"}) return } clt.SetAccount(account) go runner.StartDownload(job, job.Link, clt, db) runner.Broadcast() c.Status(http.StatusNoContent) } } func HandlePauseJob() gin.HandlerFunc { return func(c *gin.Context) { id := c.Param("id") runner.UpdateJobStatus(id, "paused", nil) runner.Broadcast() c.Status(http.StatusNoContent) } } func HandleResumeJob(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { id := c.Param("id") jobsMu.Lock() job, exists := jobs[id] jobsMu.Unlock() if !exists { var j runner.DownloadJob if err := db.First(&j, "id = ?", id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Job introuvable"}) return } jobCopy := j jobsMu.Lock() jobs[id] = &jobCopy job = &jobCopy jobsMu.Unlock() } clt := client.NewClient(db) account := runner.GetFirstActiveAccount(clt) if account == nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Aucun compte actif"}) return } clt.SetAccount(account) go runner.StartDownload(job, job.Link, clt, db) runner.Broadcast() c.Status(http.StatusNoContent) } } func HandleDeleteJob(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { id := c.Param("id") runner.DeleteJob(id, db) go runner.Broadcast() c.Status(http.StatusNoContent) } } func HandleListJobs(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { var jobs []runner.DownloadJob // Optionnel : filtrage (par user, status, etc.) if err := db.Find(&jobs).Error; err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } c.JSON(200, gin.H{ "jobs": jobs, }) } }