shelfy/internal/download/jobs.go
2025-06-12 17:31:12 +02:00

195 lines
4.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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"` // 0100
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 dun 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 dun 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, "_")
}
//***//