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 +}