package main import ( "encoding/json" "fmt" "io" "net/http" "net/url" "os" "path/filepath" "strings" ) type ProgressInfo struct { Percentage float64 CurrentFile string Paused bool } func handleRoot(w http.ResponseWriter, r *http.Request) { progressMutex.Lock() defer progressMutex.Unlock() jobsInfo := make(map[string]struct { Percentage float64 CurrentFile string Paused bool }) for filename, info := range progress { jobsInfo[filename] = struct { Percentage float64 CurrentFile string Paused bool }{ Percentage: info.Percentage, CurrentFile: info.CurrentFile, Paused: info.Paused, } } err := templates.ExecuteTemplate(w, "index", struct { Jobs map[string]struct { Percentage float64 CurrentFile string Paused bool } }{jobsInfo}) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } func handleUpload(w http.ResponseWriter, r *http.Request) { err := r.ParseMultipartForm(32 << 20) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } files := r.MultipartForm.File["files"] if len(files) == 0 { http.Error(w, "No files uploaded", http.StatusBadRequest) return } uploadedFiles := []string{} for _, fileHeader := range files { file, err := fileHeader.Open() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } defer file.Close() tempFile, err := os.CreateTemp(uploadDir, fileHeader.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) return } uploadedFiles = append(uploadedFiles, filepath.Base(tempFile.Name())) _, err = parseInputFile(tempFile.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?files="+url.QueryEscape(strings.Join(validFiles, ",")), http.StatusSeeOther) } func handleSelect(w http.ResponseWriter, r *http.Request) { 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 } sortItems(items) groupedItems := groupItemsBySeason(items) allItems[filename] = groupedItems } if len(allItems) == 0 { http.Error(w, "No valid files were processed", http.StatusBadRequest) return } err := templates.ExecuteTemplate(w, "select", struct { Filenames string AllItems map[string]map[string][]Item }{ Filenames: filesParam, AllItems: allItems, }) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } func handleProcess(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } selectedItems := r.Form["items"] if len(selectedItems) == 0 { http.Error(w, "No items selected", http.StatusBadRequest) return } 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 { http.Error(w, err.Error(), http.StatusInternalServerError) return } selectedItems := filterSelectedItems(allItems, items) sortItems(selectedItems) go processItems(filename, selectedItems) } http.Redirect(w, r, "/", http.StatusSeeOther) } func handleProgress(w http.ResponseWriter, r *http.Request) { filename := r.URL.Query().Get("filename") if r.Header.Get("Accept") == "application/json" { progressInfo := getProgress(filename) if progressInfo == nil { w.WriteHeader(http.StatusNotFound) json.NewEncoder(w).Encode(map[string]string{"error": "No progress information found"}) return } w.Header().Set("Content-Type", "application/json") err := json.NewEncoder(w).Encode(progressInfo) if err != nil { http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } return } err := templates.ExecuteTemplate(w, "progress", struct{ Filename string }{filename}) if err != nil { 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() } progressMutex.Lock() if progressInfo, ok := progress[filename]; ok { progressInfo.Paused = true } progressMutex.Unlock() 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{}{} progressMutex.Lock() if progressInfo, ok := progress[filename]; ok { progressInfo.Paused = false } progressMutex.Unlock() 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() } if jobInfo.TempDir != "" { os.RemoveAll(jobInfo.TempDir) } 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) } } } func updateProgress(filename string, value float64, currentFile string) { progressMutex.Lock() defer progressMutex.Unlock() jobsMutex.Lock() jobInfo, exists := jobs[filename] jobsMutex.Unlock() paused := false if exists { paused = jobInfo.Paused } if existingProgress, ok := progress[filename]; ok { existingProgress.Percentage = value existingProgress.CurrentFile = currentFile existingProgress.Paused = paused } else { progress[filename] = &ProgressInfo{ Percentage: value, CurrentFile: currentFile, Paused: paused, } } }