222 lines
6.3 KiB
Go
222 lines
6.3 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"sync"
|
|
"syscall"
|
|
"up/utils"
|
|
)
|
|
|
|
// Глобальное состояние загрузки (аналог g_upload в C)
|
|
type UploadState struct {
|
|
mu sync.RWMutex
|
|
Type string
|
|
Received int64
|
|
Progress int
|
|
Status string
|
|
}
|
|
|
|
var uploadState UploadState
|
|
|
|
// handleUpload обрабатывает 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")
|
|
// Ограничиваем размер запроса (максимум UBOOT_MAX_SIZE + запас)
|
|
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() // очищаем временные файлы
|
|
|
|
// Получаем поле "type"
|
|
typeVals := r.MultipartForm.Value["type"]
|
|
if len(typeVals) == 0 {
|
|
http.Error(w, `{"error":"Missing type field"}`, http.StatusBadRequest)
|
|
return
|
|
}
|
|
typ := typeVals[0]
|
|
|
|
// Получаем файл из поля "file"
|
|
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 из query и декодируем
|
|
timestamp := r.URL.Query().Get("timestamp")
|
|
if timestamp != "" {
|
|
decoded, err := url.QueryUnescape(timestamp)
|
|
if err == nil {
|
|
timestamp = decoded
|
|
}
|
|
}
|
|
|
|
// Специальная обработка для U-Boot
|
|
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 чанки
|
|
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()
|
|
}
|
|
|
|
// Запись файла версии с timestamp
|
|
if timestamp != "" {
|
|
versionPath := filepath.Join(MOUNT_POINT, filename+".version")
|
|
vf, err := os.Create(versionPath)
|
|
if err == nil {
|
|
// Форматируем ISO 8601 в читаемый вид (как в C)
|
|
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)
|
|
|
|
// Формируем JSON-ответ
|
|
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,
|
|
})
|
|
}
|