initial commit
This commit is contained in:
commit
85d50ee958
26 files changed
+2146
No files matched your search
@@ -0,0 +1,221 @@
|
||||
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,
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user