2025-06-12 08:57:10 +00:00
|
|
|
|
package download
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"app/shelfly/internal/debridlink"
|
2025-06-12 15:31:12 +00:00
|
|
|
|
"io"
|
|
|
|
|
|
"log"
|
2025-06-12 08:57:10 +00:00
|
|
|
|
"net/http"
|
2025-06-12 15:31:12 +00:00
|
|
|
|
"os"
|
|
|
|
|
|
"path/filepath"
|
|
|
|
|
|
"regexp"
|
2025-06-12 08:57:10 +00:00
|
|
|
|
"sync"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
"gorm.io/gorm"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
type DownloadJob struct {
|
2025-06-12 15:31:12 +00:00
|
|
|
|
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
|
2025-06-12 08:57:10 +00:00
|
|
|
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
|
|
|
|
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-12 15:31:12 +00:00
|
|
|
|
|
2025-06-12 08:57:10 +00:00
|
|
|
|
var (
|
|
|
|
|
|
jobs = make(map[string]*DownloadJob)
|
|
|
|
|
|
jobsMu sync.Mutex
|
|
|
|
|
|
)
|
2025-06-12 15:31:12 +00:00
|
|
|
|
|
|
|
|
|
|
// Enregistre un job en mémoire et en base
|
|
|
|
|
|
func RegisterJobWithDB(job *DownloadJob, db *gorm.DB) error {
|
2025-06-12 08:57:10 +00:00
|
|
|
|
jobsMu.Lock()
|
2025-06-12 15:31:12 +00:00
|
|
|
|
jobs[job.ID] = job
|
2025-06-12 08:57:10 +00:00
|
|
|
|
jobsMu.Unlock()
|
|
|
|
|
|
|
2025-06-12 15:31:12 +00:00
|
|
|
|
log.Printf("[JOB] Enregistré : %s (%s)\n", job.Name, job.ID)
|
|
|
|
|
|
return db.Create(job).Error
|
2025-06-12 08:57:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-12 15:31:12 +00:00
|
|
|
|
// 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
|
2025-06-12 08:57:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
jobsMu.Lock()
|
|
|
|
|
|
defer jobsMu.Unlock()
|
2025-06-12 15:31:12 +00:00
|
|
|
|
|
|
|
|
|
|
for _, j := range jobList {
|
|
|
|
|
|
jobCopy := j
|
|
|
|
|
|
jobs[j.ID] = &jobCopy
|
2025-06-12 08:57:10 +00:00
|
|
|
|
}
|
2025-06-12 15:31:12 +00:00
|
|
|
|
log.Printf("[JOB] %d jobs rechargés depuis la base\n", len(jobs))
|
|
|
|
|
|
return nil
|
2025-06-12 08:57:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-12 15:31:12 +00:00
|
|
|
|
// Met à jour le status d’un job et le persiste
|
|
|
|
|
|
func UpdateJobStatus(id string, status string, db *gorm.DB) {
|
2025-06-12 08:57:10 +00:00
|
|
|
|
jobsMu.Lock()
|
|
|
|
|
|
defer jobsMu.Unlock()
|
2025-06-12 15:31:12 +00:00
|
|
|
|
|
|
|
|
|
|
if job, ok := jobs[id]; ok {
|
|
|
|
|
|
job.Status = status
|
|
|
|
|
|
job.UpdatedAt = time.Now()
|
|
|
|
|
|
if db != nil {
|
|
|
|
|
|
_ = db.Save(job)
|
|
|
|
|
|
}
|
2025-06-12 08:57:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-12 15:31:12 +00:00
|
|
|
|
// Met à jour la progression d’un job et le persiste
|
|
|
|
|
|
func UpdateJobProgress(id string, progress int, db *gorm.DB) {
|
2025-06-12 08:57:10 +00:00
|
|
|
|
jobsMu.Lock()
|
|
|
|
|
|
defer jobsMu.Unlock()
|
2025-06-12 15:31:12 +00:00
|
|
|
|
|
|
|
|
|
|
if job, ok := jobs[id]; ok {
|
|
|
|
|
|
job.Progress = progress
|
|
|
|
|
|
job.UpdatedAt = time.Now()
|
|
|
|
|
|
if db != nil {
|
|
|
|
|
|
_ = db.Save(job)
|
|
|
|
|
|
}
|
2025-06-12 08:57:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-12 15:31:12 +00:00
|
|
|
|
// Supprime un job (mémoire uniquement)
|
2025-06-12 08:57:10 +00:00
|
|
|
|
func DeleteJob(id string) {
|
|
|
|
|
|
jobsMu.Lock()
|
|
|
|
|
|
defer jobsMu.Unlock()
|
|
|
|
|
|
|
2025-06-12 15:31:12 +00:00
|
|
|
|
delete(jobs, id)
|
2025-06-12 08:57:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-12 15:31:12 +00:00
|
|
|
|
// Liste tous les jobs
|
|
|
|
|
|
func ListJobs() []*DownloadJob {
|
|
|
|
|
|
jobsMu.Lock()
|
|
|
|
|
|
defer jobsMu.Unlock()
|
2025-06-12 08:57:10 +00:00
|
|
|
|
|
2025-06-12 15:31:12 +00:00
|
|
|
|
list := make([]*DownloadJob, 0, len(jobs))
|
|
|
|
|
|
for _, job := range jobs {
|
|
|
|
|
|
list = append(list, job)
|
|
|
|
|
|
}
|
|
|
|
|
|
return list
|
2025-06-12 08:57:10 +00:00
|
|
|
|
}
|
2025-06-12 15:31:12 +00:00
|
|
|
|
const downloadDir = "./downloads"
|
2025-06-12 08:57:10 +00:00
|
|
|
|
|
2025-06-12 15:31:12 +00:00
|
|
|
|
func StartDownload(job *DownloadJob, downloadURL string, client *debridlink.Client, db *gorm.DB) {
|
|
|
|
|
|
UpdateJobStatus(job.ID, "running", db)
|
2025-06-12 08:57:10 +00:00
|
|
|
|
|
2025-06-12 15:31:12 +00:00
|
|
|
|
resp, err := http.Get(downloadURL)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
log.Printf("[ERROR] Téléchargement échoué : %v\n", err)
|
|
|
|
|
|
UpdateJobStatus(job.ID, "failed", db)
|
|
|
|
|
|
return
|
2025-06-12 08:57:10 +00:00
|
|
|
|
}
|
2025-06-12 15:31:12 +00:00
|
|
|
|
defer resp.Body.Close()
|
2025-06-12 08:57:10 +00:00
|
|
|
|
|
2025-06-12 15:31:12 +00:00
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
|
|
log.Printf("[ERROR] Erreur HTTP : %s\n", resp.Status)
|
|
|
|
|
|
UpdateJobStatus(job.ID, "failed", db)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-06-12 08:57:10 +00:00
|
|
|
|
|
2025-06-12 15:31:12 +00:00
|
|
|
|
// 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)
|
2025-06-12 08:57:10 +00:00
|
|
|
|
if err != nil {
|
2025-06-12 15:31:12 +00:00
|
|
|
|
log.Printf("[ERROR] Impossible de créer le fichier : %v\n", err)
|
|
|
|
|
|
UpdateJobStatus(job.ID, "failed", db)
|
|
|
|
|
|
return
|
2025-06-12 08:57:10 +00:00
|
|
|
|
}
|
2025-06-12 15:31:12 +00:00
|
|
|
|
defer outFile.Close()
|
|
|
|
|
|
|
|
|
|
|
|
// Taille totale
|
|
|
|
|
|
totalSize := resp.ContentLength
|
|
|
|
|
|
if totalSize <= 0 && job.Size > 0 {
|
|
|
|
|
|
totalSize = job.Size
|
2025-06-12 08:57:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-12 15:31:12 +00:00
|
|
|
|
// Téléchargement avec suivi de progression
|
|
|
|
|
|
buf := make([]byte, 32*1024) // 32KB
|
|
|
|
|
|
var downloaded int64
|
|
|
|
|
|
lastUpdate := time.Now()
|
2025-06-12 08:57:10 +00:00
|
|
|
|
|
2025-06-12 15:31:12 +00:00
|
|
|
|
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)
|
2025-06-12 08:57:10 +00:00
|
|
|
|
}
|
2025-06-12 15:31:12 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
if err == io.EOF {
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
log.Printf("[ERROR] Erreur de lecture : %v\n", err)
|
|
|
|
|
|
UpdateJobStatus(job.ID, "failed", db)
|
2025-06-12 08:57:10 +00:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-12 15:31:12 +00:00
|
|
|
|
// 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()
|
|
|
|
|
|
}
|
2025-06-12 08:57:10 +00:00
|
|
|
|
}
|
2025-06-12 15:31:12 +00:00
|
|
|
|
|
|
|
|
|
|
// 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, "_")
|
2025-06-12 08:57:10 +00:00
|
|
|
|
}
|
2025-06-12 15:31:12 +00:00
|
|
|
|
//***//
|
2025-06-12 08:57:10 +00:00
|
|
|
|
|