package api import ( "encoding/json" "fmt" "io" "net/http" "net/url" "os" "path/filepath" "strconv" "sync" "syscall" "up/utils" ) type UploadState struct { mu sync.RWMutex Type string Received int64 Progress int Status string } var uploadState UploadState // POST /api/upload func HandleUpload(w http.ResponseWriter, r *http.Request) { uboot_max_size_int, err := strconv.Atoi(os.Getenv("UBOOT_MAX_SIZE")) if err != nil { fmt.Println("Error during conversion:", err) return } max_file_size_int, err := strconv.Atoi(os.Getenv("MAX_FILE_SIZE")) if err != nil { fmt.Println("error during conversion: ", err) return } UBOOT_MAX_SIZE := uboot_max_size_int * 1024 * 1024 MAX_FILE_SIZE := max_file_size_int * 1024 * 1024 MOUNT_POINT := os.Getenv("MOUNT_POINT") r.Body = http.MaxBytesReader(w, r.Body, int64(MAX_FILE_SIZE)+1024) if err := r.ParseMultipartForm(32 << 20); err != nil { // 32 MB в памяти http.Error(w, `{"error":"Failed to parse multipart form"}`, http.StatusBadRequest) return } defer r.MultipartForm.RemoveAll() typeVals := r.MultipartForm.Value["type"] if len(typeVals) == 0 { http.Error(w, `{"error":"Missing type field"}`, http.StatusBadRequest) return } typ := typeVals[0] files := r.MultipartForm.File["file"] if len(files) == 0 { http.Error(w, `{"error":"Missing file field"}`, http.StatusBadRequest) return } fileHeader := files[0] maxSize := int64(MAX_FILE_SIZE) if typ == "uboot_bin" { maxSize = int64(UBOOT_MAX_SIZE) } if fileHeader.Size > maxSize { http.Error(w, `{"error":"File too large"}`, http.StatusBadRequest) return } srcFile, err := fileHeader.Open() if err != nil { http.Error(w, `{"error":"Failed to open uploaded file"}`, http.StatusInternalServerError) return } defer srcFile.Close() if utils.MountDevice() < 0 { http.Error(w, `{"error":"Failed to mount device"}`, http.StatusInternalServerError) return } filename := utils.GetTargetFilename(typ) if filename == "" { http.Error(w, `{"error":"Invalid upload type"}`, http.StatusBadRequest) return } destPath := filepath.Join(MOUNT_POINT, filename) uploadState.mu.Lock() uploadState.Type = typ uploadState.Received = 0 uploadState.Progress = 0 uploadState.Status = "Uploading..." uploadState.mu.Unlock() fmt.Printf("Upload: %s (%d bytes) -> %s\n", typ, fileHeader.Size, destPath) timestamp := r.URL.Query().Get("timestamp") if timestamp != "" { decoded, err := url.QueryUnescape(timestamp) if err == nil { timestamp = decoded } } if typ == "uboot_bin" { data, err := io.ReadAll(srcFile) if err != nil { uploadState.mu.Lock() uploadState.Status = "Failed" uploadState.mu.Unlock() http.Error(w, `{"error":"Failed to read U-Boot data"}`, http.StatusInternalServerError) return } if err := utils.WriteUbootToDevices(data, destPath); err != nil { uploadState.mu.Lock() uploadState.Status = "Failed" uploadState.mu.Unlock() fmt.Println("error: ", err) http.Error(w, `{"error":"Failed to write U-Boot"}`, http.StatusInternalServerError) return } uploadState.mu.Lock() uploadState.Received = int64(len(data)) uploadState.Progress = 100 uploadState.Status = "Complete" uploadState.mu.Unlock() } else { destFile, err := os.Create(destPath) if err != nil { uploadState.mu.Lock() uploadState.Status = "Failed" uploadState.mu.Unlock() http.Error(w, `{"error":"Failed to create file"}`, http.StatusInternalServerError) return } defer destFile.Close() buf := make([]byte, 65536) // 64 KB chunks var written int64 for { n, err := srcFile.Read(buf) if n > 0 { if _, err := destFile.Write(buf[:n]); err != nil { uploadState.mu.Lock() uploadState.Status = "Failed" uploadState.mu.Unlock() http.Error(w, `{"error":"Failed to write file"}`, http.StatusInternalServerError) return } written += int64(n) uploadState.mu.Lock() uploadState.Received = written uploadState.Progress = int(written * 100 / fileHeader.Size) uploadState.mu.Unlock() } if err == io.EOF { break } if err != nil { uploadState.mu.Lock() uploadState.Status = "Failed" uploadState.mu.Unlock() http.Error(w, `{"error":"Failed to read uploaded file"}`, http.StatusInternalServerError) return } } destFile.Sync() syscall.Sync() uploadState.mu.Lock() uploadState.Progress = 100 uploadState.Status = "Complete" uploadState.mu.Unlock() } if timestamp != "" { versionPath := filepath.Join(MOUNT_POINT, filename+".version") vf, err := os.Create(versionPath) if err == nil { var readableDate string if len(timestamp) >= 19 { readableDate = timestamp[:10] + " " + timestamp[11:19] // "YYYY-MM-DD HH:MM:SS" } else { readableDate = timestamp } vf.WriteString(readableDate + "\n") vf.Close() fmt.Printf("Written version file: %s (%s)\n", versionPath, readableDate) } } fmt.Printf("Upload complete: %s\n", destPath) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]interface{}{ "status": "success", "type": typ, "filename": filename, "size": fileHeader.Size, "progress": 100, }) }