From 707de8fcf15253684784747bc2c9a7fd29471b43 Mon Sep 17 00:00:00 2001 From: Joren Date: Sat, 14 Sep 2024 02:02:35 +0200 Subject: [PATCH] Support uploading multiple files --- handlers.go | 150 ++++++++++++++++++++++++++++++++--------------- templates/index | 2 +- templates/select | 33 ++++++----- utils.go | 24 +++++--- 4 files changed, 138 insertions(+), 71 deletions(-) diff --git a/handlers.go b/handlers.go index 11f5ce3..6fcd0c8 100644 --- a/handlers.go +++ b/handlers.go @@ -5,8 +5,10 @@ import ( "fmt" "io" "net/http" + "net/url" "os" "path/filepath" + "strings" ) type ProgressInfo struct { @@ -50,53 +52,101 @@ func handleRoot(w http.ResponseWriter, r *http.Request) { } func handleUpload(w http.ResponseWriter, r *http.Request) { - file, header, err := r.FormFile("file") + err := r.ParseMultipartForm(32 << 20) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } - defer file.Close() - tempFile, err := os.CreateTemp(uploadDir, header.Filename) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - defer tempFile.Close() - - _, err = io.Copy(tempFile, file) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + files := r.MultipartForm.File["files"] + if len(files) == 0 { + http.Error(w, "No files uploaded", http.StatusBadRequest) return } - tempFilename := filepath.Base(tempFile.Name()) + uploadedFiles := []string{} - _, err = parseInputFile(tempFile.Name()) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + for _, fileHeader := range files { + file, err := fileHeader.Open() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer file.Close() + + dst, err := os.Create(filepath.Join(uploadDir, fileHeader.Filename)) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer dst.Close() + + _, err = io.Copy(dst, file) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + uploadedFiles = append(uploadedFiles, fileHeader.Filename) + + _, err = parseInputFile(dst.Name()) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } + + validFiles := []string{} + for _, file := range uploadedFiles { + if file != "" { + validFiles = append(validFiles, file) + } + } + + if len(validFiles) == 0 { + http.Error(w, "No valid files were uploaded", http.StatusBadRequest) return } - http.Redirect(w, r, "/select?filename="+tempFilename, http.StatusSeeOther) + http.Redirect(w, r, "/select?files="+url.QueryEscape(strings.Join(validFiles, ",")), 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) + filesParam := r.URL.Query().Get("files") + filenames := strings.Split(filesParam, ",") + + allItems := make(map[string]map[string][]Item) + + for _, filename := range filenames { + if filename == "" { + continue + } + fullPath := filepath.Join(uploadDir, filename) + + if _, err := os.Stat(fullPath); os.IsNotExist(err) { + continue + } + + items, err := parseInputFile(fullPath) + if err != nil { + continue + } + + groupedItems := groupItemsBySeason(items) + allItems[filename] = groupedItems + } + + if len(allItems) == 0 { + http.Error(w, "No valid files were processed", http.StatusBadRequest) return } - groupedItems := groupItemsBySeason(items) - - err = templates.ExecuteTemplate(w, "select", struct { - Filename string - Items map[string][]Item + err := templates.ExecuteTemplate(w, "select", struct { + Filenames string + AllItems map[string]map[string][]Item }{ - Filename: filename, - Items: groupedItems, + Filenames: filesParam, + AllItems: allItems, }) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -104,36 +154,47 @@ func handleSelect(w http.ResponseWriter, r *http.Request) { } 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) + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) return } - filteredItems := filterSelectedItems(items, selectedItems) + selectedItems := r.Form["items"] + if len(selectedItems) == 0 { + http.Error(w, "No items selected", http.StatusBadRequest) + return + } - go func() { - err := processItems(filename, filteredItems) + itemsByFile := make(map[string][]string) + for _, item := range selectedItems { + parts := strings.SplitN(item, ":", 2) + if len(parts) != 2 { + continue + } + filename, itemName := parts[0], parts[1] + itemsByFile[filename] = append(itemsByFile[filename], itemName) + } + + for filename, items := range itemsByFile { + fullPath := filepath.Join(uploadDir, filename) + allItems, err := parseInputFile(fullPath) if err != nil { - fmt.Printf("Error processing file: %v\n", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return } - os.Remove(filepath.Join(uploadDir, filename)) - }() + selectedItems := filterSelectedItems(allItems, items) + go processItems(filename, selectedItems) + } - http.Redirect(w, r, "/progress?filename="+filename, http.StatusSeeOther) + http.Redirect(w, r, "/", http.StatusSeeOther) } func handleProgress(w http.ResponseWriter, r *http.Request) { filename := r.URL.Query().Get("filename") - fmt.Printf("Handling progress request for filename: %s\n", filename) if r.Header.Get("Accept") == "application/json" { progressInfo := getProgress(filename) - fmt.Printf("Progress info for %s: %+v\n", filename, progressInfo) if progressInfo == nil { w.WriteHeader(http.StatusNotFound) @@ -144,7 +205,6 @@ func handleProgress(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") err := json.NewEncoder(w).Encode(progressInfo) if err != nil { - fmt.Printf("Error encoding progress info: %v\n", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } @@ -206,7 +266,6 @@ func handleResume(w http.ResponseWriter, r *http.Request) { jobInfo.Paused = false jobInfo.ResumeChan <- struct{}{} - // Update the progress information progressMutex.Lock() if progressInfo, ok := progress[filename]; ok { progressInfo.Paused = false @@ -291,5 +350,4 @@ func updateProgress(filename string, value float64, currentFile string) { Paused: paused, } } - fmt.Printf("Progress updated for %s: %.2f%%, Current file: %s, Paused: %v\n", filename, value, currentFile, paused) } diff --git a/templates/index b/templates/index index cc55ff1..af57727 100644 --- a/templates/index +++ b/templates/index @@ -112,7 +112,7 @@

Simple Downloader

- +

Currently Running Jobs

diff --git a/templates/select b/templates/select index 4527b29..4d9fb3b 100644 --- a/templates/select +++ b/templates/select @@ -55,22 +55,25 @@

Select Items to Download

- - {{range $season, $items := .Items}} -
-
- - -
- {{range $item := $items}} -
- + + {{range $filename, $fileItems := .AllItems}} +

{{$filename}}

+ {{range $season, $items := $fileItems}} +
+
+ +
- {{end}} -
+ {{range $item := $items}} +
+ +
+ {{end}} +
+ {{end}} {{end}}
diff --git a/utils.go b/utils.go index 6fc3cf7..42d8733 100644 --- a/utils.go +++ b/utils.go @@ -138,16 +138,24 @@ func findOrCreateSegmentTimeline(adaptationSet *etree.Element) *etree.Element { return segmentTemplate.CreateElement("SegmentTimeline") } -func parseInputFile(inputFile string) ([]Item, error) { - jsonFile, err := os.Open(inputFile) +func parseInputFile(filename string) ([]Item, error) { + fileInfo, err := os.Stat(filename) if err != nil { - return nil, fmt.Errorf("error opening file %s: %v", inputFile, err) + return nil, err + } + if fileInfo.IsDir() { + return nil, fmt.Errorf("%s is a directory", filename) } - defer jsonFile.Close() - byteValue, err := io.ReadAll(jsonFile) + file, err := os.Open(filename) if err != nil { - return nil, fmt.Errorf("error reading file %s: %v", inputFile, err) + return nil, err + } + defer file.Close() + + byteValue, err := io.ReadAll(file) + if err != nil { + return nil, err } byteValue = removeBOM(byteValue) @@ -155,7 +163,7 @@ func parseInputFile(inputFile string) ([]Item, error) { var items Items err = json.Unmarshal(byteValue, &items) if err != nil { - return nil, fmt.Errorf("error unmarshaling JSON: %v", err) + return nil, err } return items.Items, nil @@ -217,7 +225,6 @@ func processItems(filename string, items []Item) error { 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") @@ -231,7 +238,6 @@ func processItems(filename string, items []Item) error { i-- continue } - fmt.Printf("Error downloading file: %v\n", err) } } }