maj , update est teste a prevoir
This commit is contained in:
parent
463c5b8d10
commit
e0d753a927
@ -425,14 +425,36 @@ func (c *Client) RemoveTorrents(ctx context.Context, ids []string) error {
|
|||||||
|
|
||||||
// =========================== Downloader ===========================
|
// =========================== Downloader ===========================
|
||||||
type Link struct {
|
type Link struct {
|
||||||
ID string `json:"id" gorm:"column:id;primaryKey"`
|
ID string `json:"id" gorm:"primaryKey;column:id"`
|
||||||
OriginalLink string `json:"originalLink" gorm:"column:original_link"`
|
Name string `json:"name" gorm:"column:name"`
|
||||||
DownloadLink string `json:"downloadLink" gorm:"column:download_link"`
|
URL string `json:"url" gorm:"column:url"` // Lien d'origine
|
||||||
Host string `json:"host" gorm:"column:host"`
|
DownloadURL string `json:"downloadUrl" gorm:"column:download_url"` // Lien débridé direct
|
||||||
Status string `json:"status" gorm:"column:status"`
|
Host string `json:"host" gorm:"column:host"` // Nom de l'hébergeur
|
||||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
Size int64 `json:"size" gorm:"column:size"` // Taille en octets
|
||||||
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
Chunk int `json:"chunk" gorm:"column:chunk"` // Nombre de chunks
|
||||||
|
Expired bool `json:"expired" gorm:"column:expired"` // Lien expiré ou non
|
||||||
|
Created int64 `json:"created" gorm:"column:created"` // Timestamp
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
}
|
}
|
||||||
|
type StreamInfo struct {
|
||||||
|
ID string `json:"id" gorm:"primaryKey;column:id"`
|
||||||
|
StreamURL string `json:"streamUrl" gorm:"column:stream_url"`
|
||||||
|
DownloadURL string `json:"downloadUrl" gorm:"column:download_url"`
|
||||||
|
Type string `json:"type" gorm:"column:type"` // hls ou mp4
|
||||||
|
MimeType string `json:"mimetype" gorm:"column:mimetype"` // ex: video/mp4
|
||||||
|
Domain string `json:"domain" gorm:"column:domain"`
|
||||||
|
|
||||||
|
// Champs du fichier lié (ex : nom de la vidéo)
|
||||||
|
FileID string `json:"-" gorm:"column:file_id"` // lien avec le champ File.ID ci-dessous
|
||||||
|
FileName string `json:"-" gorm:"column:file_name"` // nom fichier
|
||||||
|
FileSize int64 `json:"-" gorm:"column:file_size"` // taille fichier
|
||||||
|
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func (c *Client) ListLinks(ctx context.Context) ([]Link, error) {
|
func (c *Client) ListLinks(ctx context.Context) ([]Link, error) {
|
||||||
var links []Link
|
var links []Link
|
||||||
@ -441,16 +463,41 @@ func (c *Client) ListLinks(ctx context.Context) ([]Link, error) {
|
|||||||
}
|
}
|
||||||
return links, nil
|
return links, nil
|
||||||
}
|
}
|
||||||
func (c *Client) AddLink(ctx context.Context, link string) (*Link, error) {
|
func (c *Client) AddLink(ctx context.Context, link string) ([]Link, error) {
|
||||||
var result Link
|
var envelope struct {
|
||||||
body := map[string]string{"url": link} // ✅ CORRECTION
|
Success bool `json:"success"`
|
||||||
if err := c.doJSON(ctx, "POST", "downloader/add", nil, body, &result); err != nil { // ✅ CORRECTION
|
Value json.RawMessage `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
body := map[string]string{"url": link}
|
||||||
|
|
||||||
|
// Requête brute
|
||||||
|
if err := c.doJSON(ctx, "POST", "downloader/add", nil, body, &envelope); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &result, nil
|
|
||||||
|
var links []Link
|
||||||
|
|
||||||
|
switch envelope.Value[0] {
|
||||||
|
case '{':
|
||||||
|
var single Link
|
||||||
|
if err := json.Unmarshal(envelope.Value, &single); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
links = append(links, single)
|
||||||
|
case '[':
|
||||||
|
if err := json.Unmarshal(envelope.Value, &links); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, errors.New("format de réponse inattendu")
|
||||||
|
}
|
||||||
|
|
||||||
|
return links, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func (c *Client) RemoveLinks(ctx context.Context, ids []string) error {
|
func (c *Client) RemoveLinks(ctx context.Context, ids []string) error {
|
||||||
body := map[string][]string{"ids": ids}
|
body := map[string][]string{"ids": ids}
|
||||||
return c.doJSON(ctx, "DELETE", "downloader/links", nil, body, nil)
|
return c.doJSON(ctx, "DELETE", "downloader/links", nil, body, nil)
|
||||||
@ -467,6 +514,7 @@ type File struct {
|
|||||||
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (c *Client) ListFiles(ctx context.Context, parentID string) ([]File, error) {
|
func (c *Client) ListFiles(ctx context.Context, parentID string) ([]File, error) {
|
||||||
var files []File
|
var files []File
|
||||||
path := fmt.Sprintf("files/%s", parentID)
|
path := fmt.Sprintf("files/%s", parentID)
|
||||||
@ -486,11 +534,39 @@ func (c *Client) CreateTranscode(ctx context.Context, fileID, preset string) (st
|
|||||||
return resp.TranscodeID, nil
|
return resp.TranscodeID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetTranscode(ctx context.Context, transcodeID string) (map[string]interface{}, error) {
|
func (c *Client) GetTranscode(ctx context.Context, transcodeID string) (*StreamInfo, error) {
|
||||||
var result map[string]interface{}
|
var raw struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Value struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
StreamURL string `json:"streamUrl"`
|
||||||
|
DownloadURL string `json:"downloadUrl"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
MimeType string `json:"mimetype"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
File struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
} `json:"file"`
|
||||||
|
} `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
path := fmt.Sprintf("stream/transcode/%s", transcodeID)
|
path := fmt.Sprintf("stream/transcode/%s", transcodeID)
|
||||||
if err := c.doJSON(ctx, "GET", path, nil, nil, &result); err != nil {
|
if err := c.doJSON(ctx, "GET", path, nil, nil, &raw); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return result, nil
|
|
||||||
|
info := &StreamInfo{
|
||||||
|
ID: raw.Value.ID,
|
||||||
|
StreamURL: raw.Value.StreamURL,
|
||||||
|
DownloadURL: raw.Value.DownloadURL,
|
||||||
|
Type: raw.Value.Type,
|
||||||
|
MimeType: raw.Value.MimeType,
|
||||||
|
Domain: raw.Value.Domain,
|
||||||
|
FileID: raw.Value.File.ID,
|
||||||
|
FileName: raw.Value.File.Name,
|
||||||
|
FileSize: raw.Value.File.Size,
|
||||||
|
}
|
||||||
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
package download
|
package download
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"app/shelfly/internal/debridlink"
|
||||||
"app/shelfly/internal/models"
|
"app/shelfly/internal/models"
|
||||||
"app/shelfly/query"
|
"app/shelfly/query"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -15,6 +17,23 @@ import (
|
|||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
func GetFirstActiveAccount(client *debridlink.Client) *debridlink.DebridAccount {
|
||||||
|
ctx := context.Background() // ✅ on remplace ici
|
||||||
|
|
||||||
|
accounts, err := client.ListDebridAccounts(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Impossible de récupérer les comptes :", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, acc := range accounts {
|
||||||
|
if acc.IsActive {
|
||||||
|
return &acc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func CreateSavePath(db *gorm.DB) http.HandlerFunc {
|
func CreateSavePath(db *gorm.DB) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
@ -1,232 +1,194 @@
|
|||||||
// internal/download/jobs.go
|
|
||||||
package download
|
package download
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"app/shelfly/internal/debridlink"
|
"app/shelfly/internal/debridlink"
|
||||||
"context"
|
"io"
|
||||||
"encoding/json"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DownloadJob struct {
|
type DownloadJob struct {
|
||||||
ID uint `gorm:"primaryKey"`
|
ID string `gorm:"primaryKey;column:id"`
|
||||||
RemoteID string `gorm:"index"`
|
Link string `gorm:"column:link"`
|
||||||
FileName string `gorm:"size:255"`
|
Name string `gorm:"column:name"`
|
||||||
Status string `gorm:"size:50"`
|
Status string `gorm:"column:status"` // waiting, running, done, failed, paused
|
||||||
Speed string `gorm:"size:50"`
|
PathID uint `gorm:"column:path_id"`
|
||||||
StreamURL string `gorm:"-" json:"stream_url"`
|
Size int64 `gorm:"column:size"`
|
||||||
ErrorMsg string `gorm:"-" json:"error_msg"`
|
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"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
Link string `gorm:"size:512"`
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
jobs = make(map[string]*DownloadJob)
|
jobs = make(map[string]*DownloadJob)
|
||||||
jobsMu sync.Mutex
|
jobsMu sync.Mutex
|
||||||
)
|
)
|
||||||
func AddJob(link string, pathID uint, db *gorm.DB) *DownloadJob {
|
|
||||||
id := time.Now().UnixNano()
|
// Enregistre un job en mémoire et en base
|
||||||
job := &DownloadJob{
|
func RegisterJobWithDB(job *DownloadJob, db *gorm.DB) error {
|
||||||
ID: uint(id),
|
|
||||||
Link: link, // ← important !
|
|
||||||
RemoteID: "",
|
|
||||||
FileName: extractFileName(link),
|
|
||||||
Status: "added",
|
|
||||||
Speed: "",
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
UpdatedAt: time.Now(),
|
|
||||||
}
|
|
||||||
jobsMu.Lock()
|
jobsMu.Lock()
|
||||||
jobs[strconv.FormatUint(uint64(id), 10)] = job
|
jobs[job.ID] = job
|
||||||
jobsMu.Unlock()
|
jobsMu.Unlock()
|
||||||
|
|
||||||
db.Create(job)
|
log.Printf("[JOB] Enregistré : %s (%s)\n", job.Name, job.ID)
|
||||||
return job
|
return db.Create(job).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Charge tous les jobs depuis la base en mémoire (au démarrage)
|
||||||
|
func InitJobsFromDB(db *gorm.DB) error {
|
||||||
func startDownload(job *DownloadJob, link string, client *debridlink.Client, db *gorm.DB) {
|
var jobList []DownloadJob
|
||||||
ctx := context.Background()
|
if err := db.Find(&jobList).Error; err != nil {
|
||||||
job.Status = "downloading"
|
return err
|
||||||
linkResp, err := client.AddLink(ctx, link)
|
|
||||||
if err != nil {
|
|
||||||
job.Status = "error"
|
|
||||||
job.ErrorMsg = err.Error()
|
|
||||||
db.Save(job)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
job.RemoteID = linkResp.ID
|
|
||||||
db.Save(job)
|
jobsMu.Lock()
|
||||||
go syncDownloadStatus(job, client, db)
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
func syncDownloadStatus(job *DownloadJob, client *debridlink.Client, db *gorm.DB) {
|
// Met à jour le status d’un job et le persiste
|
||||||
ctx := context.Background()
|
func UpdateJobStatus(id string, status string, db *gorm.DB) {
|
||||||
var checkCount int
|
jobsMu.Lock()
|
||||||
for {
|
defer jobsMu.Unlock()
|
||||||
if job.Status != "downloading" {
|
|
||||||
return
|
if job, ok := jobs[id]; ok {
|
||||||
|
job.Status = status
|
||||||
|
job.UpdatedAt = time.Now()
|
||||||
|
if db != nil {
|
||||||
|
_ = db.Save(job)
|
||||||
}
|
}
|
||||||
links, err := client.ListLinks(ctx)
|
|
||||||
if err != nil {
|
|
||||||
job.Status = "error"
|
|
||||||
job.ErrorMsg = err.Error()
|
|
||||||
db.Save(job)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, l := range links {
|
|
||||||
if l.ID == job.RemoteID {
|
|
||||||
job.Status = l.Status
|
|
||||||
checkCount++
|
|
||||||
if checkCount%2 == 0 {
|
|
||||||
job.Speed = "~2 MB/s"
|
|
||||||
} else {
|
|
||||||
job.Speed = "~1.4 MB/s"
|
|
||||||
}
|
|
||||||
if l.Status == "downloaded" {
|
|
||||||
files, err := client.ListFiles(ctx, l.ID)
|
|
||||||
if err == nil && len(files) > 0 {
|
|
||||||
transcodeID, err := client.CreateTranscode(ctx, files[0].ID, "original")
|
|
||||||
if err == nil {
|
|
||||||
job.StreamURL = "https://debrid-link.com/stream/" + transcodeID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
db.Save(job)
|
|
||||||
if l.Status == "downloaded" || l.Status == "error" {
|
|
||||||
_ = client.RemoveLinks(ctx, []string{l.ID})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
time.Sleep(2 * time.Second)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 {
|
func ListJobs() []*DownloadJob {
|
||||||
jobsMu.Lock()
|
jobsMu.Lock()
|
||||||
defer jobsMu.Unlock()
|
defer jobsMu.Unlock()
|
||||||
var list []*DownloadJob
|
|
||||||
|
list := make([]*DownloadJob, 0, len(jobs))
|
||||||
for _, job := range jobs {
|
for _, job := range jobs {
|
||||||
list = append(list, job)
|
list = append(list, job)
|
||||||
}
|
}
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
const downloadDir = "./downloads"
|
||||||
|
|
||||||
func PauseJob(id string) {
|
func StartDownload(job *DownloadJob, downloadURL string, client *debridlink.Client, db *gorm.DB) {
|
||||||
jobsMu.Lock()
|
UpdateJobStatus(job.ID, "running", db)
|
||||||
defer jobsMu.Unlock()
|
|
||||||
if job, ok := jobs[id]; ok && job.Status == "downloading" {
|
|
||||||
job.Status = "paused"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ResumeJob(id string, client *debridlink.Client, db *gorm.DB) {
|
resp, err := http.Get(downloadURL)
|
||||||
jobsMu.Lock()
|
|
||||||
defer jobsMu.Unlock()
|
|
||||||
if job, ok := jobs[id]; ok && job.Status == "paused" {
|
|
||||||
job.Status = "downloading"
|
|
||||||
db.Save(job)
|
|
||||||
go syncDownloadStatus(job, client, db)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteJob(id string) {
|
|
||||||
jobsMu.Lock()
|
|
||||||
defer jobsMu.Unlock()
|
|
||||||
delete(jobs, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateID() string {
|
|
||||||
return time.Now().Format("20060102150405.000")
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractFileName(link string) string {
|
|
||||||
return "file_from_" + link
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func HandleListJobs(w http.ResponseWriter, r *http.Request) {
|
|
||||||
jobs := ListJobs()
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
json.NewEncoder(w).Encode(jobs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandlePauseJob(w http.ResponseWriter, r *http.Request) {
|
|
||||||
id := mux.Vars(r)["id"]
|
|
||||||
PauseJob(id)
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleResumeJob(db *gorm.DB) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
id := mux.Vars(r)["id"]
|
|
||||||
client := debridlink.NewClient(db)
|
|
||||||
account := getFirstActiveAccount(client)
|
|
||||||
if account == nil {
|
|
||||||
http.Error(w, "Aucun compte actif", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
client.SetAccount(account)
|
|
||||||
ResumeJob(id, client, db)
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleDeleteJob(w http.ResponseWriter, r *http.Request) {
|
|
||||||
id := mux.Vars(r)["id"]
|
|
||||||
DeleteJob(id)
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFirstActiveAccount(client *debridlink.Client) *debridlink.DebridAccount {
|
|
||||||
ctx := context.Background()
|
|
||||||
accounts, err := client.ListDebridAccounts(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
log.Printf("[ERROR] Téléchargement échoué : %v\n", err)
|
||||||
|
UpdateJobStatus(job.ID, "failed", db)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
for _, acc := range accounts {
|
defer resp.Body.Close()
|
||||||
if acc.IsActive {
|
|
||||||
return &acc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func HandleStartJob(db *gorm.DB) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
id := mux.Vars(r)["id"]
|
|
||||||
|
|
||||||
jobsMu.Lock()
|
if resp.StatusCode != http.StatusOK {
|
||||||
job, exists := jobs[id]
|
log.Printf("[ERROR] Erreur HTTP : %s\n", resp.Status)
|
||||||
jobsMu.Unlock()
|
UpdateJobStatus(job.ID, "failed", db)
|
||||||
|
|
||||||
if !exists {
|
|
||||||
http.Error(w, "Job not found", http.StatusNotFound)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
client := debridlink.NewClient(db)
|
// Créer le fichier de destination
|
||||||
account := getFirstActiveAccount(client)
|
if err := os.MkdirAll(downloadDir, os.ModePerm); err != nil {
|
||||||
if account == nil {
|
log.Printf("[ERROR] Création du dossier %s échouée : %v\n", downloadDir, err)
|
||||||
http.Error(w, "Aucun compte actif", http.StatusBadRequest)
|
UpdateJobStatus(job.ID, "failed", db)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
client.SetAccount(account)
|
destPath := filepath.Join(downloadDir, sanitizeFileName(job.Name))
|
||||||
|
outFile, err := os.Create(destPath)
|
||||||
go startDownload(job, job.Link, client, db)
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] Impossible de créer le fichier : %v\n", err)
|
||||||
w.WriteHeader(http.StatusNoContent)
|
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, "_")
|
||||||
|
}
|
||||||
|
//***//
|
||||||
|
|
||||||
|
|||||||
@ -56,13 +56,12 @@ func RoutesProtected(r *mux.Router, bd *gorm.DB) {
|
|||||||
r.HandleFunc("/godownloader/settings/delete", renders.GoDownloadSettingDelete(bd))
|
r.HandleFunc("/godownloader/settings/delete", renders.GoDownloadSettingDelete(bd))
|
||||||
r.HandleFunc("/api/download/add", renders.HandleAddJob(bd)).Methods("POST")
|
r.HandleFunc("/api/download/add", renders.HandleAddJob(bd)).Methods("POST")
|
||||||
r.HandleFunc("/api/download/all", renders.HandleListJobsPartial(bd)).Methods("GET")
|
r.HandleFunc("/api/download/all", renders.HandleListJobsPartial(bd)).Methods("GET")
|
||||||
r.HandleFunc("/api/download/pause/{id}", download.HandlePauseJob).Methods("POST")
|
|
||||||
r.HandleFunc("/api/download/resume/{id}", download.HandleResumeJob(bd)).Methods("POST")
|
|
||||||
r.HandleFunc("/api/download/delete/{id}", download.HandleDeleteJob).Methods("DELETE")
|
|
||||||
r.HandleFunc("/stream/{id}", download.HandleStreamPage()).Methods("GET")
|
|
||||||
r.HandleFunc("/downloads", renders.GoDownload2(bd))
|
r.HandleFunc("/downloads", renders.GoDownload2(bd))
|
||||||
r.HandleFunc("/api/download/start/{id}", download.HandleStartJob(bd)).Methods("POST")
|
r.HandleFunc("/stream/{id}", download.HandleStreamPage()).Methods("GET")
|
||||||
|
r.HandleFunc("/api/download/start/{id}", renders.HandleStartJob(bd)).Methods("POST")
|
||||||
|
r.HandleFunc("/api/download/pause/{id}", renders.HandlePauseJob).Methods("POST")
|
||||||
|
r.HandleFunc("/api/download/resume/{id}", renders.HandleResumeJob(bd)).Methods("POST")
|
||||||
|
r.HandleFunc("/api/download/delete/{id}", renders.HandleDeleteJob).Methods("DELETE")
|
||||||
|
|
||||||
|
|
||||||
// API user
|
// API user
|
||||||
|
|||||||
@ -9,9 +9,11 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -258,7 +260,11 @@ func GoDownload2(db *gorm.DB) http.HandlerFunc {
|
|||||||
|
|
||||||
func HandleAddJob(db *gorm.DB) http.HandlerFunc {
|
func HandleAddJob(db *gorm.DB) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
r.ParseForm()
|
if err := r.ParseForm(); err != nil {
|
||||||
|
http.Error(w, "Requête invalide", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
link := r.FormValue("link")
|
link := r.FormValue("link")
|
||||||
pathIDStr := r.FormValue("path_id")
|
pathIDStr := r.FormValue("path_id")
|
||||||
|
|
||||||
@ -268,17 +274,51 @@ func HandleAddJob(db *gorm.DB) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = download.AddJob(link, uint(parsedID), db)
|
log.Println("[HTTP] Lien reçu :", link)
|
||||||
|
log.Println("[HTTP] ID de chemin :", parsedID)
|
||||||
|
|
||||||
// Mise à jour de la vue partielle du tableau
|
// Authentification Debrid-Link
|
||||||
jobs := download.ListJobs()
|
client := debridlink.NewClient(db)
|
||||||
data := map[string]interface{}{
|
account := download.GetFirstActiveAccount(client)
|
||||||
"jobs": jobs,
|
if account == nil {
|
||||||
|
http.Error(w, "Aucun compte Debrid-Link actif", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client.SetAccount(account)
|
||||||
|
|
||||||
|
// Débride le lien
|
||||||
|
ctx := r.Context()
|
||||||
|
links, err := client.AddLink(ctx, link)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] Echec lors de l'ajout du lien : %v\n", err)
|
||||||
|
http.Error(w, "Erreur côté Debrid-Link", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enregistre chaque lien comme un job "en attente"
|
||||||
|
for _, l := range links {
|
||||||
|
job := &download.DownloadJob{
|
||||||
|
ID: l.ID,
|
||||||
|
Link: l.DownloadURL,
|
||||||
|
Name: l.Name,
|
||||||
|
Status: "waiting",
|
||||||
|
PathID: uint(parsedID),
|
||||||
|
Size: l.Size,
|
||||||
|
Host: l.Host,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
download.RegisterJobWithDB(job,db) // => stocke en mémoire ou DB selon ton implémentation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Met à jour la vue partielle (tableau des jobs)
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"jobs": download.ListJobs(),
|
||||||
|
}
|
||||||
renderPartial(w, "downloads_table", data)
|
renderPartial(w, "downloads_table", data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func HandleListJobsPartial(db *gorm.DB) http.HandlerFunc {
|
func HandleListJobsPartial(db *gorm.DB) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
jobs := download.ListJobs()
|
jobs := download.ListJobs()
|
||||||
@ -288,6 +328,72 @@ func HandleListJobsPartial(db *gorm.DB) http.HandlerFunc {
|
|||||||
renderPartial(w, "downloads_table", data)
|
renderPartial(w, "downloads_table", data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var (
|
||||||
|
jobs = make(map[string]*download.DownloadJob)
|
||||||
|
jobsMu sync.Mutex
|
||||||
|
)
|
||||||
|
func HandleStartJob(db *gorm.DB) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
id := mux.Vars(r)["id"]
|
||||||
|
|
||||||
|
jobsMu.Lock()
|
||||||
|
job, exists := jobs[id]
|
||||||
|
jobsMu.Unlock()
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
http.Error(w, "Job introuvable", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client := debridlink.NewClient(db)
|
||||||
|
account := download.GetFirstActiveAccount(client)
|
||||||
|
if account == nil {
|
||||||
|
http.Error(w, "Aucun compte actif", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client.SetAccount(account)
|
||||||
|
|
||||||
|
go download.StartDownload(job, job.Link, client, db)
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func HandlePauseJob(w http.ResponseWriter, r *http.Request) {
|
||||||
|
id := mux.Vars(r)["id"]
|
||||||
|
download.UpdateJobStatus(id, "paused", nil)
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
func HandleResumeJob(db *gorm.DB) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
id := mux.Vars(r)["id"]
|
||||||
|
|
||||||
|
jobsMu.Lock()
|
||||||
|
job, exists := jobs[id]
|
||||||
|
jobsMu.Unlock()
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
http.Error(w, "Job introuvable", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client := debridlink.NewClient(db)
|
||||||
|
account := download.GetFirstActiveAccount(client)
|
||||||
|
if account == nil {
|
||||||
|
http.Error(w, "Aucun compte actif", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client.SetAccount(account)
|
||||||
|
|
||||||
|
go download.StartDownload(job, job.Link, client, db)
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func HandleDeleteJob(w http.ResponseWriter, r *http.Request) {
|
||||||
|
id := mux.Vars(r)["id"]
|
||||||
|
download.DeleteJob(id)
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// func GoDownloadSetting(db *gorm.DB) http.HandlerFunc {
|
// func GoDownloadSetting(db *gorm.DB) http.HandlerFunc {
|
||||||
// return func(w http.ResponseWriter, r *http.Request) {
|
// return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
BIN
shelfly_db.db
BIN
shelfly_db.db
Binary file not shown.
Loading…
Reference in New Issue
Block a user