From 916d3004de0ae0cd860273fe01a340d84b11df09 Mon Sep 17 00:00:00 2001 From: Joren Date: Fri, 13 Sep 2024 22:00:44 +0200 Subject: [PATCH 1/7] Episode Selector --- downloaders.go | 33 ------------------------- handlers.go | 49 ++++++++++++++++++++++++++++++++++--- main.go | 9 ++++++- utils.go | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 119 insertions(+), 37 deletions(-) diff --git a/downloaders.go b/downloaders.go index dd77c35..4816773 100644 --- a/downloaders.go +++ b/downloaders.go @@ -2,7 +2,6 @@ package main import ( "encoding/base64" - "encoding/json" "fmt" "io" "net/http" @@ -12,38 +11,6 @@ import ( "strings" ) -func processInputFile(inputFile string) error { - jsonFile, err := os.Open(inputFile) - if err != nil { - return fmt.Errorf("error opening file %s: %v", inputFile, err) - } - defer jsonFile.Close() - - byteValue, err := io.ReadAll(jsonFile) - if err != nil { - return fmt.Errorf("error reading file %s: %v", inputFile, err) - } - - byteValue = removeBOM(byteValue) - - var items Items - err = json.Unmarshal(byteValue, &items) - if err != nil { - return fmt.Errorf("error unmarshaling JSON: %v", err) - } - - for i, item := range items.Items { - updateProgress(filepath.Base(inputFile), float64(i)/float64(len(items.Items))*100, item.Filename) - err := downloadFile(item) - if err != nil { - fmt.Printf("Error downloading file: %v\n", err) - } - } - updateProgress(filepath.Base(inputFile), 100, "") - - return nil -} - func removeBOM(input []byte) []byte { if len(input) >= 3 && input[0] == 0xEF && input[1] == 0xBB && input[2] == 0xBF { return input[3:] diff --git a/handlers.go b/handlers.go index d7df463..c1c1642 100644 --- a/handlers.go +++ b/handlers.go @@ -51,16 +51,59 @@ func handleUpload(w http.ResponseWriter, r *http.Request) { tempFilename := filepath.Base(tempFile.Name()) + _, err = parseInputFile(tempFile.Name()) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + http.Redirect(w, r, "/select?filename="+tempFilename, http.StatusSeeOther) +} + +func handleSelect(w http.ResponseWriter, r *http.Request) { + filename := r.URL.Query().Get("filename") + items, err := parseInputFile(filepath.Join(uploadDir, filename)) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + groupedItems := groupItemsBySeason(items) + + err = templates.ExecuteTemplate(w, "select", struct { + Filename string + Items map[string][]Item + }{ + Filename: filename, + Items: groupedItems, + }) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } +} + +func handleProcess(w http.ResponseWriter, r *http.Request) { + filename := r.FormValue("filename") + selectedItems := r.Form["items"] + + items, err := parseInputFile(filepath.Join(uploadDir, filename)) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + filteredItems := filterSelectedItems(items, selectedItems) + go func() { - err := processInputFile(tempFile.Name()) + err := processItems(filename, filteredItems) if err != nil { fmt.Printf("Error processing file: %v\n", err) } - os.Remove(tempFile.Name()) + os.Remove(filepath.Join(uploadDir, filename)) }() - http.Redirect(w, r, "/progress?filename="+tempFilename, http.StatusSeeOther) + http.Redirect(w, r, "/progress?filename="+filename, http.StatusSeeOther) } func handleProgress(w http.ResponseWriter, r *http.Request) { diff --git a/main.go b/main.go index 74fcf39..3a490a4 100644 --- a/main.go +++ b/main.go @@ -63,13 +63,20 @@ func main() { if *inputFile == "" { startWebServer() } else { - processInputFile(*inputFile) + items, err := parseInputFile(*inputFile) + if err != nil { + fmt.Printf("Error parsing input file: %v\n", err) + return + } + processItems(*inputFile, items) } } func startWebServer() { http.HandleFunc("/", handleRoot) http.HandleFunc("/upload", handleUpload) + http.HandleFunc("/select", handleSelect) + http.HandleFunc("/process", handleProcess) http.HandleFunc("/progress", handleProgress) fmt.Println("Starting web server on http://0.0.0.0:8080") diff --git a/utils.go b/utils.go index 321a237..31351c3 100644 --- a/utils.go +++ b/utils.go @@ -1,8 +1,11 @@ package main import ( + "encoding/json" "fmt" + "io" "net/url" + "os" "regexp" "strconv" "strings" @@ -118,3 +121,65 @@ func findOrCreateSegmentTimeline(adaptationSet *etree.Element) *etree.Element { segmentTemplate := representation.CreateElement("SegmentTemplate") return segmentTemplate.CreateElement("SegmentTimeline") } + +func parseInputFile(inputFile string) ([]Item, error) { + jsonFile, err := os.Open(inputFile) + if err != nil { + return nil, fmt.Errorf("error opening file %s: %v", inputFile, err) + } + defer jsonFile.Close() + + byteValue, err := io.ReadAll(jsonFile) + if err != nil { + return nil, fmt.Errorf("error reading file %s: %v", inputFile, err) + } + + byteValue = removeBOM(byteValue) + + var items Items + err = json.Unmarshal(byteValue, &items) + if err != nil { + return nil, fmt.Errorf("error unmarshaling JSON: %v", err) + } + + return items.Items, nil +} + +func groupItemsBySeason(items []Item) map[string][]Item { + grouped := make(map[string][]Item) + for _, item := range items { + metadata := parseMetadata(item.Metadata) + if metadata.Type == "serie" { + key := fmt.Sprintf("%s - %s", metadata.Title, metadata.Season) + grouped[key] = append(grouped[key], item) + } else { + grouped["Movies"] = append(grouped["Movies"], item) + } + } + return grouped +} + +func filterSelectedItems(items []Item, selectedItems []string) []Item { + var filtered []Item + for _, item := range items { + for _, selected := range selectedItems { + if item.Filename == selected { + filtered = append(filtered, item) + break + } + } + } + return filtered +} + +func processItems(filename string, items []Item) error { + for i, item := range items { + updateProgress(filename, float64(i)/float64(len(items))*100, item.Filename) + err := downloadFile(item) + if err != nil { + fmt.Printf("Error downloading file: %v\n", err) + } + } + updateProgress(filename, 100, "") + return nil +} From 8c010665e149d78c41156c9342e0e6fcabe7dfa9 Mon Sep 17 00:00:00 2001 From: Joren Date: Fri, 13 Sep 2024 22:09:00 +0200 Subject: [PATCH 2/7] Selectp age --- templates/select | 102 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 templates/select diff --git a/templates/select b/templates/select new file mode 100644 index 0000000..4527b29 --- /dev/null +++ b/templates/select @@ -0,0 +1,102 @@ + + + + + + Select Items to Download + + + +

Select Items to Download

+
+ + {{range $season, $items := .Items}} +
+
+ + +
+ {{range $item := $items}} +
+ +
+ {{end}} +
+ {{end}} +
+ + + +
+
+ + + From 5397ba090726e53180891c061b8ba08498b8045a Mon Sep 17 00:00:00 2001 From: Joren Date: Fri, 13 Sep 2024 22:15:20 +0200 Subject: [PATCH 3/7] Abort (kinda) --- main.go | 21 +++++++++++++++++++++ templates/progress | 24 ++++++++++++++++++++++++ utils.go | 31 +++++++++++++++++++++++++++---- 3 files changed, 72 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index 3a490a4..3eb5277 100644 --- a/main.go +++ b/main.go @@ -78,6 +78,7 @@ func startWebServer() { http.HandleFunc("/select", handleSelect) http.HandleFunc("/process", handleProcess) http.HandleFunc("/progress", handleProgress) + http.HandleFunc("/abort", handleAbort) fmt.Println("Starting web server on http://0.0.0.0:8080") http.ListenAndServe(":8080", nil) @@ -114,3 +115,23 @@ func parseMetadata(metadata string) Metadata { Season: "S" + strings.TrimSpace(parts[2]), } } + +func handleAbort(w http.ResponseWriter, r *http.Request) { + filename := r.URL.Query().Get("filename") + if filename == "" { + http.Error(w, "Filename is required", http.StatusBadRequest) + return + } + + jobsMutex.Lock() + abortChan, exists := jobs[filename] + jobsMutex.Unlock() + + if !exists { + http.Error(w, "Job not found", http.StatusNotFound) + return + } + + close(abortChan) + fmt.Fprintf(w, "Abort signal sent for %s", filename) +} diff --git a/templates/progress b/templates/progress index e5c05f4..5cd3bed 100644 --- a/templates/progress +++ b/templates/progress @@ -56,6 +56,18 @@ margin-top: 10px; word-wrap: break-word; } + #abort-button { + background-color: #f44336; + color: white; + border: none; + padding: 10px 15px; + margin-top: 10px; + border-radius: 4px; + cursor: pointer; + } + #abort-button:hover { + background-color: #d32f2f; + } @media (max-width: 600px) { body { padding: 10px; @@ -84,6 +96,7 @@
+ diff --git a/utils.go b/utils.go index 31351c3..5c3c758 100644 --- a/utils.go +++ b/utils.go @@ -9,10 +9,16 @@ import ( "regexp" "strconv" "strings" + "sync" "github.com/beevik/etree" ) +var ( + jobsMutex sync.Mutex + jobs = make(map[string]chan struct{}) +) + func sanitizeFilename(filename string) string { filename = regexp.MustCompile(`[<>:"/\\|?*]`).ReplaceAllString(filename, "_") @@ -173,11 +179,28 @@ func filterSelectedItems(items []Item, selectedItems []string) []Item { } func processItems(filename string, items []Item) error { + jobsMutex.Lock() + abortChan := make(chan struct{}) + jobs[filename] = abortChan + jobsMutex.Unlock() + + defer func() { + jobsMutex.Lock() + delete(jobs, filename) + jobsMutex.Unlock() + }() + for i, item := range items { - updateProgress(filename, float64(i)/float64(len(items))*100, item.Filename) - err := downloadFile(item) - if err != nil { - fmt.Printf("Error downloading file: %v\n", err) + select { + case <-abortChan: + updateProgress(filename, 100, "Aborted") + return fmt.Errorf("download aborted") + default: + updateProgress(filename, float64(i)/float64(len(items))*100, item.Filename) + err := downloadFile(item) + if err != nil { + fmt.Printf("Error downloading file: %v\n", err) + } } } updateProgress(filename, 100, "") From dfe21445e551148d8c2763cf07983a9ab838b5ee Mon Sep 17 00:00:00 2001 From: Joren Date: Fri, 13 Sep 2024 22:17:16 +0200 Subject: [PATCH 4/7] Kill downloader process to instantly abort --- downloaders.go | 27 ++++++++++++++++++++++++--- go.mod | 1 + go.sum | 2 ++ main.go | 8 ++++++-- utils.go | 18 +++++++++++++----- 5 files changed, 46 insertions(+), 10 deletions(-) diff --git a/downloaders.go b/downloaders.go index 4816773..67d615c 100644 --- a/downloaders.go +++ b/downloaders.go @@ -18,7 +18,7 @@ func removeBOM(input []byte) []byte { return input } -func downloadFile(item Item) error { +func downloadFile(item Item, jobInfo *JobInfo) error { fmt.Println("Downloading:", item.Filename) mpdPath := item.MPD @@ -91,12 +91,33 @@ func downloadFile(item Item) error { cmd := exec.Command("bash", "-c", command) + jobsMutex.Lock() + jobInfo.Cmd = cmd + jobsMutex.Unlock() + cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - err := cmd.Run() + err := cmd.Start() if err != nil { - return fmt.Errorf("error executing download command: %v", err) + return fmt.Errorf("error starting download command: %v", err) + } + + done := make(chan error) + go func() { + done <- cmd.Wait() + }() + + select { + case <-jobInfo.AbortChan: + if cmd.Process != nil { + cmd.Process.Kill() + } + return fmt.Errorf("download aborted") + case err := <-done: + if err != nil { + return fmt.Errorf("error executing download command: %v", err) + } } fmt.Println("Download completed successfully") diff --git a/go.mod b/go.mod index bc9873f..7614d61 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( require ( github.com/asticode/go-astikit v0.20.0 // indirect github.com/asticode/go-astits v1.8.0 // indirect + github.com/pkg/exec v0.0.0-20150614095509-0bd164ad2a5a golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect golang.org/x/text v0.3.2 // indirect ) diff --git a/go.sum b/go.sum index 92cc1b6..97fe73a 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/beevik/etree v1.4.1 h1:PmQJDDYahBGNKDcpdX8uPy1xRCwoCGVUiW669MEirVI= github.com/beevik/etree v1.4.1/go.mod h1:gPNJNaBGVZ9AwsidazFZyygnd+0pAU38N4D+WemwKNs= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pkg/exec v0.0.0-20150614095509-0bd164ad2a5a h1:EN123kAtAAE2pg/+TvBsUBZfHCWNNFyL2ZBPPfNWAc0= +github.com/pkg/exec v0.0.0-20150614095509-0bd164ad2a5a/go.mod h1:b95YoNrAnScjaWG+asr8lxqlrsPUcT2ZEBcjvVGshMo= github.com/pkg/profile v1.4.0/go.mod h1:NWz/XGvpEW1FyYQ7fCx4dqYBLlfTcE+A9FLAkNKqjFE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= diff --git a/main.go b/main.go index 3eb5277..fe74b32 100644 --- a/main.go +++ b/main.go @@ -124,7 +124,7 @@ func handleAbort(w http.ResponseWriter, r *http.Request) { } jobsMutex.Lock() - abortChan, exists := jobs[filename] + jobInfo, exists := jobs[filename] jobsMutex.Unlock() if !exists { @@ -132,6 +132,10 @@ func handleAbort(w http.ResponseWriter, r *http.Request) { return } - close(abortChan) + close(jobInfo.AbortChan) + if jobInfo.Cmd != nil && jobInfo.Cmd.Process != nil { + jobInfo.Cmd.Process.Kill() + } + fmt.Fprintf(w, "Abort signal sent for %s", filename) } diff --git a/utils.go b/utils.go index 5c3c758..62d6ed7 100644 --- a/utils.go +++ b/utils.go @@ -6,6 +6,7 @@ import ( "io" "net/url" "os" + "os/exec" "regexp" "strconv" "strings" @@ -14,9 +15,14 @@ import ( "github.com/beevik/etree" ) +type JobInfo struct { + AbortChan chan struct{} + Cmd *exec.Cmd +} + var ( jobsMutex sync.Mutex - jobs = make(map[string]chan struct{}) + jobs = make(map[string]*JobInfo) ) func sanitizeFilename(filename string) string { @@ -180,8 +186,10 @@ func filterSelectedItems(items []Item, selectedItems []string) []Item { func processItems(filename string, items []Item) error { jobsMutex.Lock() - abortChan := make(chan struct{}) - jobs[filename] = abortChan + jobInfo := &JobInfo{ + AbortChan: make(chan struct{}), + } + jobs[filename] = jobInfo jobsMutex.Unlock() defer func() { @@ -192,12 +200,12 @@ func processItems(filename string, items []Item) error { for i, item := range items { select { - case <-abortChan: + case <-jobInfo.AbortChan: updateProgress(filename, 100, "Aborted") return fmt.Errorf("download aborted") default: updateProgress(filename, float64(i)/float64(len(items))*100, item.Filename) - err := downloadFile(item) + err := downloadFile(item, jobInfo) if err != nil { fmt.Printf("Error downloading file: %v\n", err) } From 2f9552e7719b3beb6157b465aa55002fb942711f Mon Sep 17 00:00:00 2001 From: Joren Date: Fri, 13 Sep 2024 22:29:03 +0200 Subject: [PATCH 5/7] Pause imp --- downloaders.go | 3 ++ go.mod | 1 - handlers.go | 70 +++++++++++++++++++++++++++++++++++++++++++ main.go | 26 ++-------------- templates/progress | 43 ++++++++++++++++++++++++++ utils.go | 75 ++++++++++++++++++++++++++++++++++++++++++---- 6 files changed, 187 insertions(+), 31 deletions(-) diff --git a/downloaders.go b/downloaders.go index 67d615c..f11d206 100644 --- a/downloaders.go +++ b/downloaders.go @@ -115,6 +115,9 @@ func downloadFile(item Item, jobInfo *JobInfo) error { } return fmt.Errorf("download aborted") case err := <-done: + if jobInfo.Paused { + return fmt.Errorf("download paused") + } if err != nil { return fmt.Errorf("error executing download command: %v", err) } diff --git a/go.mod b/go.mod index 7614d61..bc9873f 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,6 @@ require ( require ( github.com/asticode/go-astikit v0.20.0 // indirect github.com/asticode/go-astits v1.8.0 // indirect - github.com/pkg/exec v0.0.0-20150614095509-0bd164ad2a5a golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect golang.org/x/text v0.3.2 // indirect ) diff --git a/handlers.go b/handlers.go index c1c1642..8d2074e 100644 --- a/handlers.go +++ b/handlers.go @@ -135,3 +135,73 @@ func handleProgress(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusInternalServerError) } } + +func handlePause(w http.ResponseWriter, r *http.Request) { + filename := r.URL.Query().Get("filename") + if filename == "" { + http.Error(w, "Filename is required", http.StatusBadRequest) + return + } + + jobsMutex.Lock() + jobInfo, exists := jobs[filename] + jobsMutex.Unlock() + + if !exists { + http.Error(w, "Job not found", http.StatusNotFound) + return + } + + jobInfo.Paused = true + if jobInfo.Cmd != nil && jobInfo.Cmd.Process != nil { + jobInfo.Cmd.Process.Kill() + } + + fmt.Fprintf(w, "Pause signal sent for %s", filename) +} + +func handleResume(w http.ResponseWriter, r *http.Request) { + filename := r.URL.Query().Get("filename") + if filename == "" { + http.Error(w, "Filename is required", http.StatusBadRequest) + return + } + + jobsMutex.Lock() + jobInfo, exists := jobs[filename] + jobsMutex.Unlock() + + if !exists { + http.Error(w, "Job not found", http.StatusNotFound) + return + } + + jobInfo.Paused = false + jobInfo.ResumeChan <- struct{}{} + + fmt.Fprintf(w, "Resume signal sent for %s", filename) +} + +func handleAbort(w http.ResponseWriter, r *http.Request) { + filename := r.URL.Query().Get("filename") + if filename == "" { + http.Error(w, "Filename is required", http.StatusBadRequest) + return + } + + jobsMutex.Lock() + jobInfo, exists := jobs[filename] + jobsMutex.Unlock() + + if !exists { + http.Error(w, "Job not found", http.StatusNotFound) + return + } + + close(jobInfo.AbortChan) + if jobInfo.Cmd != nil && jobInfo.Cmd.Process != nil { + jobInfo.Cmd.Process.Kill() + } + + fmt.Fprintf(w, "Abort signal sent for %s", filename) +} diff --git a/main.go b/main.go index fe74b32..86360aa 100644 --- a/main.go +++ b/main.go @@ -79,6 +79,8 @@ func startWebServer() { http.HandleFunc("/process", handleProcess) http.HandleFunc("/progress", handleProgress) http.HandleFunc("/abort", handleAbort) + http.HandleFunc("/pause", handlePause) + http.HandleFunc("/resume", handleResume) fmt.Println("Starting web server on http://0.0.0.0:8080") http.ListenAndServe(":8080", nil) @@ -115,27 +117,3 @@ func parseMetadata(metadata string) Metadata { Season: "S" + strings.TrimSpace(parts[2]), } } - -func handleAbort(w http.ResponseWriter, r *http.Request) { - filename := r.URL.Query().Get("filename") - if filename == "" { - http.Error(w, "Filename is required", http.StatusBadRequest) - return - } - - jobsMutex.Lock() - jobInfo, exists := jobs[filename] - jobsMutex.Unlock() - - if !exists { - http.Error(w, "Job not found", http.StatusNotFound) - return - } - - close(jobInfo.AbortChan) - if jobInfo.Cmd != nil && jobInfo.Cmd.Process != nil { - jobInfo.Cmd.Process.Kill() - } - - fmt.Fprintf(w, "Abort signal sent for %s", filename) -} diff --git a/templates/progress b/templates/progress index 5cd3bed..b7160f1 100644 --- a/templates/progress +++ b/templates/progress @@ -68,6 +68,21 @@ #abort-button:hover { background-color: #d32f2f; } + #pause-button, #resume-button { + background-color: #2196F3; + color: white; + border: none; + padding: 10px 15px; + margin-top: 10px; + border-radius: 4px; + cursor: pointer; + } + #pause-button:hover, #resume-button:hover { + background-color: #1976D2; + } + #resume-button { + display: none; + } @media (max-width: 600px) { body { padding: 10px; @@ -97,6 +112,8 @@
+ + diff --git a/utils.go b/utils.go index 62d6ed7..366af0e 100644 --- a/utils.go +++ b/utils.go @@ -7,6 +7,7 @@ import ( "net/url" "os" "os/exec" + "path/filepath" "regexp" "strconv" "strings" @@ -16,8 +17,10 @@ import ( ) type JobInfo struct { - AbortChan chan struct{} - Cmd *exec.Cmd + AbortChan chan struct{} + ResumeChan chan struct{} + Cmd *exec.Cmd + Paused bool } var ( @@ -187,7 +190,8 @@ func filterSelectedItems(items []Item, selectedItems []string) []Item { func processItems(filename string, items []Item) error { jobsMutex.Lock() jobInfo := &JobInfo{ - AbortChan: make(chan struct{}), + AbortChan: make(chan struct{}), + ResumeChan: make(chan struct{}), } jobs[filename] = jobInfo jobsMutex.Unlock() @@ -198,15 +202,30 @@ func processItems(filename string, items []Item) error { jobsMutex.Unlock() }() - for i, item := range items { + for i := 0; i < len(items); i++ { select { case <-jobInfo.AbortChan: updateProgress(filename, 100, "Aborted") return fmt.Errorf("download aborted") default: - updateProgress(filename, float64(i)/float64(len(items))*100, item.Filename) - err := downloadFile(item, jobInfo) + if jobInfo.Paused { + select { + case <-jobInfo.ResumeChan: + jobInfo.Paused = false + fmt.Printf("Resuming download for %s\n", filename) + case <-jobInfo.AbortChan: + updateProgress(filename, 100, "Aborted") + return fmt.Errorf("download aborted") + } + } + updateProgress(filename, float64(i)/float64(len(items))*100, items[i].Filename) + err := downloadFile(items[i], jobInfo) if err != nil { + if err.Error() == "download paused" { + removeCompletedEpisodes(filename, items[:i]) + i-- + continue + } fmt.Printf("Error downloading file: %v\n", err) } } @@ -214,3 +233,47 @@ func processItems(filename string, items []Item) error { updateProgress(filename, 100, "") return nil } + +func removeCompletedEpisodes(filename string, completedItems []Item) error { + inputFile := filepath.Join(uploadDir, filename) + items, err := parseInputFile(inputFile) + if err != nil { + return fmt.Errorf("error parsing input file: %v", err) + } + + remainingItems := make([]Item, 0) + for _, item := range items { + if !isItemCompleted(item, completedItems) || isLastCompletedItem(item, completedItems) { + remainingItems = append(remainingItems, item) + } + } + + updatedItems := Items{Items: remainingItems} + jsonData, err := json.MarshalIndent(updatedItems, "", " ") + if err != nil { + return fmt.Errorf("error marshaling updated items: %v", err) + } + + err = os.WriteFile(inputFile, jsonData, 0644) + if err != nil { + return fmt.Errorf("error writing updated DRMD file: %v", err) + } + + return nil +} + +func isItemCompleted(item Item, completedItems []Item) bool { + for _, completedItem := range completedItems { + if item.Filename == completedItem.Filename { + return true + } + } + return false +} + +func isLastCompletedItem(item Item, completedItems []Item) bool { + if len(completedItems) == 0 { + return false + } + return item.Filename == completedItems[len(completedItems)-1].Filename +} From 37c390f91123b38bd4259b1acae71458248e8d99 Mon Sep 17 00:00:00 2001 From: Joren Date: Fri, 13 Sep 2024 22:43:22 +0200 Subject: [PATCH 6/7] Pause n abort --- config.go | 7 ++++--- config.toml | 1 + downloaders.go | 18 +++++++++++++++--- handlers.go | 4 ++++ utils.go | 5 +++++ 5 files changed, 29 insertions(+), 6 deletions(-) diff --git a/config.go b/config.go index b481b97..b7253bb 100644 --- a/config.go +++ b/config.go @@ -9,9 +9,10 @@ import ( ) type Config struct { - BaseDir string - Format string - N_m3u8DLRE struct { + BaseDir string + Format string + TempBaseDir string + N_m3u8DLRE struct { Path string } } diff --git a/config.toml b/config.toml index 90f1faa..7fe8fdb 100644 --- a/config.toml +++ b/config.toml @@ -1,5 +1,6 @@ BaseDir = "/mnt/media" Format = "mkv" +TempBaseDir = "/tmp/nre" [N_m3u8DLRE] Path = "nre" diff --git a/downloaders.go b/downloaders.go index f11d206..3681151 100644 --- a/downloaders.go +++ b/downloaders.go @@ -21,6 +21,14 @@ func removeBOM(input []byte) []byte { func downloadFile(item Item, jobInfo *JobInfo) error { fmt.Println("Downloading:", item.Filename) + tempDir := filepath.Join(config.TempBaseDir, sanitizeFilename(item.Filename)) + err := os.MkdirAll(tempDir, 0755) + if err != nil { + return fmt.Errorf("error creating temporary directory: %v", err) + } + + jobInfo.TempDir = tempDir + mpdPath := item.MPD if !isValidURL(item.MPD) { decodedMPD, err := base64.StdEncoding.DecodeString(item.MPD) @@ -75,7 +83,7 @@ func downloadFile(item Item, jobInfo *JobInfo) error { mpdPath = tempFile.Name() } - command := getDownloadCommand(item, mpdPath) + command := getDownloadCommand(item, mpdPath, tempDir) if item.Subtitles != "" { subtitlePaths, err := downloadAndConvertSubtitles(item.Subtitles) @@ -98,7 +106,7 @@ func downloadFile(item Item, jobInfo *JobInfo) error { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - err := cmd.Start() + err = cmd.Start() if err != nil { return fmt.Errorf("error starting download command: %v", err) } @@ -113,6 +121,7 @@ func downloadFile(item Item, jobInfo *JobInfo) error { if cmd.Process != nil { cmd.Process.Kill() } + os.RemoveAll(tempDir) return fmt.Errorf("download aborted") case err := <-done: if jobInfo.Paused { @@ -124,10 +133,11 @@ func downloadFile(item Item, jobInfo *JobInfo) error { } fmt.Println("Download completed successfully") + os.RemoveAll(tempDir) return nil } -func getDownloadCommand(item Item, mpdPath string) string { +func getDownloadCommand(item Item, mpdPath string, tempDir string) string { metadata := parseMetadata(item.Metadata) keys := getKeys(item.Keys) @@ -156,6 +166,8 @@ func getDownloadCommand(item Item, mpdPath string) string { } command += fmt.Sprintf(" --save-dir \"%s\"", saveDir) + command += fmt.Sprintf(" --tmp-dir \"%s\"", tempDir) + fmt.Println(command) return command diff --git a/handlers.go b/handlers.go index 8d2074e..6e92aa5 100644 --- a/handlers.go +++ b/handlers.go @@ -203,5 +203,9 @@ func handleAbort(w http.ResponseWriter, r *http.Request) { jobInfo.Cmd.Process.Kill() } + if jobInfo.TempDir != "" { + os.RemoveAll(jobInfo.TempDir) + } + fmt.Fprintf(w, "Abort signal sent for %s", filename) } diff --git a/utils.go b/utils.go index 366af0e..6fc3cf7 100644 --- a/utils.go +++ b/utils.go @@ -21,6 +21,7 @@ type JobInfo struct { ResumeChan chan struct{} Cmd *exec.Cmd Paused bool + TempDir string } var ( @@ -200,6 +201,10 @@ func processItems(filename string, items []Item) error { jobsMutex.Lock() delete(jobs, filename) jobsMutex.Unlock() + + if jobInfo.TempDir != "" { + os.RemoveAll(jobInfo.TempDir) + } }() for i := 0; i < len(items); i++ { From 8aa915e6dcf7bcf0d9b06f7d44c1ce61dcc6406d Mon Sep 17 00:00:00 2001 From: Joren Date: Fri, 13 Sep 2024 22:57:55 +0200 Subject: [PATCH 7/7] Clear jobs --- handlers.go | 23 +++++++++++++++++++++++ main.go | 1 + templates/index | 34 ++++++++++++++++++++++++++++++++++ templates/progress | 32 ++++++++++++++++++++++++-------- 4 files changed, 82 insertions(+), 8 deletions(-) diff --git a/handlers.go b/handlers.go index 6e92aa5..76b9701 100644 --- a/handlers.go +++ b/handlers.go @@ -209,3 +209,26 @@ func handleAbort(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Abort signal sent for %s", filename) } + +func handleClearCompleted(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + clearCompletedJobs() + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]bool{"success": true}) +} + +func clearCompletedJobs() { + progressMutex.Lock() + defer progressMutex.Unlock() + + for filename, info := range progress { + if info.Percentage >= 100 { + delete(progress, filename) + } + } +} diff --git a/main.go b/main.go index 86360aa..1b66682 100644 --- a/main.go +++ b/main.go @@ -81,6 +81,7 @@ func startWebServer() { http.HandleFunc("/abort", handleAbort) http.HandleFunc("/pause", handlePause) http.HandleFunc("/resume", handleResume) + http.HandleFunc("/clear-completed", handleClearCompleted) fmt.Println("Starting web server on http://0.0.0.0:8080") http.ListenAndServe(":8080", nil) diff --git a/templates/index b/templates/index index b0e7cc1..bc57763 100644 --- a/templates/index +++ b/templates/index @@ -45,6 +45,7 @@ ul { list-style-type: none; padding: 0; + margin-bottom: 10px; } li { background-color: #2d2d2d; @@ -83,6 +84,25 @@ input[type="file"], input[type="submit"] { font-size: 16px; } + input[type="submit"], #clear-completed { + font-size: 16px; + } + } + input[type="submit"], #clear-completed { + cursor: pointer; + color: white; + border: 1px solid #444; + padding: 8px 12px; + border-radius: 4px; + margin-bottom: 10px; + max-width: 100%; + width: 100%; + } + #clear-completed { + background-color: #f44336; + } + #clear-completed:hover { + background-color: #d32f2f; } @@ -107,5 +127,19 @@
  • No active jobs
  • {{end}} + + diff --git a/templates/progress b/templates/progress index b7160f1..b113c91 100644 --- a/templates/progress +++ b/templates/progress @@ -69,7 +69,7 @@ background-color: #d32f2f; } #pause-button, #resume-button { - background-color: #2196F3; + background-color: #4CAF50; color: white; border: none; padding: 10px 15px; @@ -78,11 +78,24 @@ cursor: pointer; } #pause-button:hover, #resume-button:hover { - background-color: #1976D2; + background-color: #45a049; } #resume-button { display: none; } + #back-button { + background-color: #2196F3; + color: white; + border: none; + padding: 10px 15px; + margin-top: 10px; + border-radius: 4px; + cursor: pointer; + float: right; + } + #back-button:hover { + background-color: #1976D2; + } @media (max-width: 600px) { body { padding: 10px; @@ -111,9 +124,12 @@
    - - - +
    + + + + +