package download import ( "app/shelfly/internal/debridlink" "io" "log" "net/http" "os" "path/filepath" "regexp" "sync" "time" "gorm.io/gorm" ) type DownloadJob struct { ID string `gorm:"primaryKey;column:id"` Link string `gorm:"column:link"` Name string `gorm:"column:name"` Status string `gorm:"column:status"` // waiting, running, done, failed, paused PathID uint `gorm:"column:path_id"` Size int64 `gorm:"column:size"` Host string `gorm:"column:host"` Progress int `gorm:"column:progress"` // 0–100 StreamURL string `gorm:"column:stream_url"` // <- nouveau champ CreatedAt time.Time `gorm:"autoCreateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"` } var ( jobs = make(map[string]*DownloadJob) jobsMu sync.Mutex ) // Enregistre un job en mémoire et en base func RegisterJobWithDB(job *DownloadJob, db *gorm.DB) error { jobsMu.Lock() jobs[job.ID] = job jobsMu.Unlock() log.Printf("[JOB] Enregistré : %s (%s)\n", job.Name, job.ID) return db.Create(job).Error } // Charge tous les jobs depuis la base en mémoire (au démarrage) func InitJobsFromDB(db *gorm.DB) error { var jobList []DownloadJob if err := db.Find(&jobList).Error; err != nil { return err } jobsMu.Lock() defer jobsMu.Unlock() for _, j := range jobList { jobCopy := j jobs[j.ID] = &jobCopy } log.Printf("[JOB] %d jobs rechargés depuis la base\n", len(jobs)) return nil } // Met à jour le status d’un job et le persiste func UpdateJobStatus(id string, status string, db *gorm.DB) { jobsMu.Lock() defer jobsMu.Unlock() if job, ok := jobs[id]; ok { job.Status = status job.UpdatedAt = time.Now() if db != nil { _ = db.Save(job) } } } // Met à jour la progression d’un job et le persiste func UpdateJobProgress(id string, progress int, db *gorm.DB) { jobsMu.Lock() defer jobsMu.Unlock() if job, ok := jobs[id]; ok { job.Progress = progress job.UpdatedAt = time.Now() if db != nil { _ = db.Save(job) } } } // Supprime un job (mémoire uniquement) func DeleteJob(id string) { jobsMu.Lock() defer jobsMu.Unlock() delete(jobs, id) } // Liste tous les jobs func ListJobs() []*DownloadJob { jobsMu.Lock() defer jobsMu.Unlock() list := make([]*DownloadJob, 0, len(jobs)) for _, job := range jobs { list = append(list, job) } return list } const downloadDir = "./downloads" func StartDownload(job *DownloadJob, downloadURL string, client *debridlink.Client, db *gorm.DB) { UpdateJobStatus(job.ID, "running", db) resp, err := http.Get(downloadURL) if err != nil { log.Printf("[ERROR] Téléchargement échoué : %v\n", err) UpdateJobStatus(job.ID, "failed", db) return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { log.Printf("[ERROR] Erreur HTTP : %s\n", resp.Status) UpdateJobStatus(job.ID, "failed", db) return } // Créer le fichier de destination if err := os.MkdirAll(downloadDir, os.ModePerm); err != nil { log.Printf("[ERROR] Création du dossier %s échouée : %v\n", downloadDir, err) UpdateJobStatus(job.ID, "failed", db) return } destPath := filepath.Join(downloadDir, sanitizeFileName(job.Name)) outFile, err := os.Create(destPath) if err != nil { log.Printf("[ERROR] Impossible de créer le fichier : %v\n", err) UpdateJobStatus(job.ID, "failed", db) return } defer outFile.Close() // Taille totale totalSize := resp.ContentLength if totalSize <= 0 && job.Size > 0 { totalSize = job.Size } // Téléchargement avec suivi de progression buf := make([]byte, 32*1024) // 32KB var downloaded int64 lastUpdate := time.Now() for { n, err := resp.Body.Read(buf) if n > 0 { if _, writeErr := outFile.Write(buf[:n]); writeErr != nil { log.Printf("[ERROR] Écriture échouée : %v\n", writeErr) UpdateJobStatus(job.ID, "failed", db) return } downloaded += int64(n) } if err != nil { if err == io.EOF { break } log.Printf("[ERROR] Erreur de lecture : %v\n", err) UpdateJobStatus(job.ID, "failed", db) return } // Mise à jour de la progression toutes les 500ms if time.Since(lastUpdate) > 500*time.Millisecond && totalSize > 0 { progress := int((downloaded * 100) / totalSize) UpdateJobProgress(job.ID, progress, db) lastUpdate = time.Now() } } // 100% si on arrive ici UpdateJobProgress(job.ID, 100, db) UpdateJobStatus(job.ID, "done", db) log.Printf("[OK] Fichier téléchargé : %s\n", destPath) } func sanitizeFileName(name string) string { re := regexp.MustCompile(`[^\w\-.]`) return re.ReplaceAllString(name, "_") } //***//