From d2d9abfbce238bb04236ed90ff1d46ec72fcaa6f Mon Sep 17 00:00:00 2001 From: cangui Date: Thu, 12 Jun 2025 10:57:10 +0200 Subject: [PATCH] update --- .DS_Store | Bin 6148 -> 6148 bytes docker-compose.yml | 6 +- internal/.DS_Store | Bin 6148 -> 6148 bytes internal/db/db.go | 2 + internal/debridlink/client.go | 7 +- internal/download/download.go | 29 +++ internal/download/jobs.go | 232 +++++++++++++++++++++ internal/models/models.go | 1 + internal/route/main.go | 9 + main.go | 6 +- renders/renders.go | 48 +++++ shelfly_db.db | Bin 102400 -> 118784 bytes templates/.DS_Store | Bin 0 -> 6148 bytes templates/dashboard.pages.tmpl | 8 +- templates/downloads_table.pages.tmpl | 42 ++++ templates/godownloader_download.pages.tmpl | 76 ++++++- templates/open ./.DS_Store | Bin 0 -> 6148 bytes tmp/main | Bin 20078280 -> 20370008 bytes tmp/stdout | 2 +- 19 files changed, 455 insertions(+), 13 deletions(-) create mode 100644 internal/download/jobs.go create mode 100644 templates/.DS_Store create mode 100644 templates/downloads_table.pages.tmpl create mode 100644 templates/open ./.DS_Store diff --git a/.DS_Store b/.DS_Store index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..7b0af8029cffc7f9e7f69d0f02f1d324312ee3e2 100644 GIT binary patch delta 221 zcmZoMXfc=|#>B`mF;Q%yo}wr#0|Nsi1A_nqLk>eKLlHx9T1s*9#Kh(GAPEkJ42DF8 zJcblx$)x=J9H0)MWF}Cm1gt9$s4NGh6DG;ZP{>dUma2pa1GO-)18qnJ8wj@xrh^k~ zR4zjS(5^&=5?-J>uv@_L8xsrICL4&bZ06?R<^Vc=W8!z_$^0UUtV}>d6($>s2ygZf H*~1I~pKvn* delta 70 zcmZoMXfc=|#>AjHu~2NHo+1YW5HK<@2y9-+n8vnw1EUw?W_AvK4xj>{$am(+{342+ UKzW7)kiy9(Jj$D6L{=~Z04H+~3jhEB diff --git a/docker-compose.yml b/docker-compose.yml index a203711..b5ec9dc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,5 @@ version: '3.8' + services: shelfly: build: @@ -9,5 +10,6 @@ services: volumes: - .:/app - ./shelfly_db.db:/app/shelfly_db.db - env_file: - - .env \ No newline at end of file + dns: + - 8.8.8.8 + - 1.1.1.1 diff --git a/internal/.DS_Store b/internal/.DS_Store index b9905d206540c5087d4259dd7defbac4dceb0f80..99f6b1b40e8f8fdaab759991cad86573cbeab47e 100644 GIT binary patch delta 242 zcmZoMXfc=|#>B`mu~2NHo}wrh0|Nsi1A_nqLncFdT1s(pQht8U#D~i>K@w~X$qe}n zc?@aDl0b0=CWaJ-B%tCPkZOLTA&H>~s4|5i2T0~IWW$UDn$N%i6wYTT2MXr^ z=|qMUxa7pSZWB-On#1`(#jHSc@)=4QN*Gd6g-aQVfz}i;6vKr#KICNE%+A5j0rb+w Whu@he^NSdAfGlhP;>{i+dzb+rAv~M_ delta 101 zcmZoMXfc=|#>CJ*u~2NHo}vf~0|Nsi1A_oVQht68kj=o%kTtPTLkJ|!#E`;}lv13W y1D2ay!ss?JWASDIrthp18@e{LbMSKj&DqSz^qqM!zlb3t!(CG`BE0_VT02wd< diff --git a/internal/db/db.go b/internal/db/db.go index 01cfb4a..9fb22e2 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -2,6 +2,7 @@ package db import ( "app/shelfly/internal/debridlink" + "app/shelfly/internal/download" "app/shelfly/internal/models" "fmt" @@ -36,6 +37,7 @@ func InitDB()*gorm.DB { &debridlink.RSSItem{}, &debridlink.Torrent{}, &debridlink.DebridAccount{}, + &download.DownloadJob{}, ) diff --git a/internal/debridlink/client.go b/internal/debridlink/client.go index 93735ce..ed7d419 100644 --- a/internal/debridlink/client.go +++ b/internal/debridlink/client.go @@ -54,6 +54,7 @@ func NewClient(db *gorm.DB) *Client { } } + func (c *Client) SetAccount(account *DebridAccount) { c.account = account } @@ -440,16 +441,16 @@ func (c *Client) ListLinks(ctx context.Context) ([]Link, error) { } return links, nil } - func (c *Client) AddLink(ctx context.Context, link string) (*Link, error) { var result Link - body := map[string]string{"link": link} - if err := c.doJSON(ctx, "POST", "downloader/links", nil, body, &result); err != nil { + body := map[string]string{"url": link} // ✅ CORRECTION + if err := c.doJSON(ctx, "POST", "downloader/add", nil, body, &result); err != nil { // ✅ CORRECTION return nil, err } return &result, nil } + func (c *Client) RemoveLinks(ctx context.Context, ids []string) error { body := map[string][]string{"ids": ids} return c.doJSON(ctx, "DELETE", "downloader/links", nil, body, nil) diff --git a/internal/download/download.go b/internal/download/download.go index dd4022b..a05fe65 100644 --- a/internal/download/download.go +++ b/internal/download/download.go @@ -6,10 +6,12 @@ import ( "encoding/json" "errors" "fmt" + "html/template" "log" "net/http" "os" "strings" + "github.com/gorilla/mux" "gorm.io/gorm" ) @@ -282,3 +284,30 @@ func PathValidationHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, "Failed to encode response", http.StatusInternalServerError) } } + +type StreamPageData struct { + StreamURL string +} + +func HandleStreamPage() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + job := jobs[id] + if job == nil || job.StreamURL == "" { + http.Error(w, "Stream non disponible", http.StatusNotFound) + return + } + + tmpl := ` +Streaming + + + +` + t := template.Must(template.New("stream").Parse(tmpl)) + t.Execute(w, StreamPageData{StreamURL: job.StreamURL}) + } +} diff --git a/internal/download/jobs.go b/internal/download/jobs.go new file mode 100644 index 0000000..6b6138a --- /dev/null +++ b/internal/download/jobs.go @@ -0,0 +1,232 @@ +// internal/download/jobs.go +package download + +import ( + "app/shelfly/internal/debridlink" + "context" + "encoding/json" + "net/http" + "strconv" + "sync" + "time" + + "github.com/gorilla/mux" + "gorm.io/gorm" +) + +type DownloadJob struct { + ID uint `gorm:"primaryKey"` + RemoteID string `gorm:"index"` + FileName string `gorm:"size:255"` + Status string `gorm:"size:50"` + Speed string `gorm:"size:50"` + StreamURL string `gorm:"-" json:"stream_url"` + ErrorMsg string `gorm:"-" json:"error_msg"` + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + Link string `gorm:"size:512"` + +} + +var ( + jobs = make(map[string]*DownloadJob) + jobsMu sync.Mutex +) +func AddJob(link string, pathID uint, db *gorm.DB) *DownloadJob { + id := time.Now().UnixNano() + job := &DownloadJob{ + ID: uint(id), + Link: link, // ← important ! + RemoteID: "", + FileName: extractFileName(link), + Status: "added", + Speed: "", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + jobsMu.Lock() + jobs[strconv.FormatUint(uint64(id), 10)] = job + jobsMu.Unlock() + + db.Create(job) + return job +} + + + +func startDownload(job *DownloadJob, link string, client *debridlink.Client, db *gorm.DB) { + ctx := context.Background() + job.Status = "downloading" + 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) + go syncDownloadStatus(job, client, db) +} + +func syncDownloadStatus(job *DownloadJob, client *debridlink.Client, db *gorm.DB) { + ctx := context.Background() + var checkCount int + for { + if job.Status != "downloading" { + return + } + 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) + } +} + +func ListJobs() []*DownloadJob { + jobsMu.Lock() + defer jobsMu.Unlock() + var list []*DownloadJob + for _, job := range jobs { + list = append(list, job) + } + return list +} + +func PauseJob(id string) { + jobsMu.Lock() + 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) { + 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 { + return nil + } + for _, acc := range accounts { + 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() + job, exists := jobs[id] + jobsMu.Unlock() + + if !exists { + http.Error(w, "Job not found", http.StatusNotFound) + return + } + + client := debridlink.NewClient(db) + account := getFirstActiveAccount(client) + if account == nil { + http.Error(w, "Aucun compte actif", http.StatusBadRequest) + return + } + client.SetAccount(account) + + go startDownload(job, job.Link, client, db) + + w.WriteHeader(http.StatusNoContent) + } +} + diff --git a/internal/models/models.go b/internal/models/models.go index 07b7c94..cea2907 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -2,6 +2,7 @@ package models + type User struct { ID uint `json:"id" gorm:"primaryKey"` Username string `json:"username" gorm:"size:255"` diff --git a/internal/route/main.go b/internal/route/main.go index fa33fbd..04a3f6e 100644 --- a/internal/route/main.go +++ b/internal/route/main.go @@ -54,6 +54,15 @@ func RoutesProtected(r *mux.Router, bd *gorm.DB) { r.HandleFunc("/godownloader/poll-status", renders.PollStatusHandler(bd)) r.HandleFunc("/godownloader/table-refresh", renders.GoDownloadPartialTable(bd)) r.HandleFunc("/godownloader/settings/delete", renders.GoDownloadSettingDelete(bd)) + r.HandleFunc("/api/download/add", renders.HandleAddJob(bd)).Methods("POST") + 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("/api/download/start/{id}", download.HandleStartJob(bd)).Methods("POST") + // API user diff --git a/main.go b/main.go index 24aff3f..de47522 100644 --- a/main.go +++ b/main.go @@ -9,14 +9,10 @@ import ( "net/http" "github.com/gorilla/mux" - "github.com/joho/godotenv" ) func main() { - err := godotenv.Load() - if err != nil { - log.Fatal("Erreur de chargement du fichier .env") - } + // 1. Démarrer le routeur principal r := mux.NewRouter() diff --git a/renders/renders.go b/renders/renders.go index 44d27ea..84278c8 100644 --- a/renders/renders.go +++ b/renders/renders.go @@ -2,6 +2,7 @@ package renders import ( "app/shelfly/internal/debridlink" + "app/shelfly/internal/download" "app/shelfly/internal/models" "context" "encoding/json" @@ -240,6 +241,53 @@ func PollStatusHandler(db *gorm.DB) http.HandlerFunc { }) } } +func GoDownload2(db *gorm.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + jobs := download.ListJobs() + var paths []models.PathDownload + db.Find(&paths) + + data := map[string]interface{}{ + "jobs": jobs, + "paths": paths, + } + + renderTemplate(w, "godownloader_download", data) + } +} + +func HandleAddJob(db *gorm.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + link := r.FormValue("link") + pathIDStr := r.FormValue("path_id") + + parsedID, err := strconv.Atoi(pathIDStr) + if err != nil { + http.Error(w, "Chemin invalide", http.StatusBadRequest) + return + } + + _ = download.AddJob(link, uint(parsedID), db) + + // Mise à jour de la vue partielle du tableau + jobs := download.ListJobs() + data := map[string]interface{}{ + "jobs": jobs, + } + + renderPartial(w, "downloads_table", data) + } +} +func HandleListJobsPartial(db *gorm.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + jobs := download.ListJobs() + data := map[string]interface{}{ + "jobs": jobs, + } + renderPartial(w, "downloads_table", data) + } +} // func GoDownloadSetting(db *gorm.DB) http.HandlerFunc { // return func(w http.ResponseWriter, r *http.Request) { diff --git a/shelfly_db.db b/shelfly_db.db index f1024b68ce5bda614aaa87e5becd98ae10e5b117..73b0e53f3e3d6e3014df18ec36f7f7f47c140f9f 100644 GIT binary patch delta 2014 zcmb`ITWB0r7{_OJH#6<-&ddf&H%+>_ULZ}Aojr3oGjsMO))t=4hgFa++ z`{ZKf6$;-|I!vF%tCBwVlFt?Nx|k)5`IY&eSz(q+-N6r--X52;pHQ2kT3{91c3ia6ochu8Hut08k)`RSfl=Kb$P8! z{y%D?ok>Iyfny~F%1DMt?u1>HNG!)8NkTm2>XI!`FWvA2VolOsGdSWbpelq9~Rvu%w4SU~{@ z@;nq<*qf!kTs&!-s~b=oTaIjwi*O2y6dMZgQpd=)B~W33pg>*}C4M7n&6izFHGJlJ|*m#_b}wh-FD*PUE# z+^r&URn^q?bOdq)h&GYH@%DX}`X1;u32X$>=F*WOikQ>g^^G~hHHxlT8MJFJDpoGy zE~4lhv)*y65k%4P+(^JO9X+K*XP`QO_mw_z-N9Y-qxfR!0`WFUzv}nng=h76BBf0c z)v8^>AMug%7^Bh^IzjzO4O6?kIq$INy2tPS(LLe5f5-X`KRIvL;0$RPy`+DnA1^$Z z%&1yEspg|?1wES6(pg=LCe^m?z{oSB2PQ{@PmVu6`a&?a!L(TL(0DM`HaWI0)>`$6 z*gzq^hxGUNRMhOiq+1NOgybMJ7)E` z{?cr#byU+-3-|7_#+?s%W2t0jram-KtUQccZ#X3D5BxoA&dFI<99OKObK1J(60Kg> z8S7lvkoB|cvh_SYRVosT_-=Rgm}Aak%rDGO%(u)}%;!v*Id316^US2jNfFJ1v2Let s-cibA-A=t#K5;eNu=});!5ejZ=8K0;n5zwY=|f`-Htdssl@~()00OyoF8}}l delta 92 zcmZozz}~QcZGyDm0R{#JNg##+sfjwqJO>!`$~t-Z92g`Ru1;rPz-TyGP{4BI)oNZw qiS2>w7&kL6W)V=}V&*@?!2gy1A^(}pf(rZio6p#9KV#2m5&!`1TNyC` diff --git a/templates/.DS_Store b/templates/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..d617ffe9461a079600d046352c714ac1062436f9 GIT binary patch literal 6148 zcmeHKJ8A<#43!dN7SgzMIVUjq27|FrkP8I=D&q}=^jqazIhvn77|eJZgB$Y%(wouf z&9bjptwcmuzx$=gOhh`kq5N5B&Cbmmd&`Uh;kcv7;<*3WZTtSzRNqe+cPhPn%R$Z$ z@bBNsEES*vRDcRl0V*)2fc0Kj_YufQ1*iZOxGG@ZhXOaOi9?`&IuLvW08Ws0!`f#F zV6g$L^`1-F`ixEa