shelfy/internal/download/jobs.go

195 lines
4.5 KiB
Go
Raw Normal View History

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"` // 0100
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 dun 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 dun 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