package api import ( "encoding/json" "net/http" "time" "jukebox_maker/internal/config" "jukebox_maker/internal/disk" ) func (s *Server) diskResponse(info disk.DiskInfo) map[string]any { item := map[string]any{ "state": info.State, "disk_id": info.DiskID, "total_bytes": info.TotalBytes, "free_bytes": info.FreeBytes, "mount_path": info.MountPath, "profile": info.Profile, } if info.DiskID != "" { if s.deps.OnDiskInit != nil { s.deps.OnDiskInit(info.MountPath, info.DiskID) } if lastCopiedAt, ok, err := s.deps.Copier.LastCopiedAt(info.DiskID); err == nil && ok { item["last_copied_at"] = lastCopiedAt.Format(time.RFC3339) } if t, ok := s.deps.Tasks.ActiveTaskByDisk(info.DiskID); ok { item["active_task_id"] = t.ID } } return item } func (s *Server) handleDiskStatus(w http.ResponseWriter, r *http.Request) { type response struct { State disk.DiskState `json:"state"` DiskID string `json:"disk_id"` TotalBytes int64 `json:"total_bytes"` FreeBytes int64 `json:"free_bytes"` MountPath string `json:"mount_path"` LastCopiedAt string `json:"last_copied_at,omitempty"` ActiveTaskID string `json:"active_task_id,omitempty"` } disks := s.deps.Watcher.ListDisks() resp := make([]response, 0, len(disks)) for _, info := range disks { item := response{ State: info.State, DiskID: info.DiskID, TotalBytes: info.TotalBytes, FreeBytes: info.FreeBytes, MountPath: info.MountPath, } if payload := s.diskResponse(info); payload != nil { if v, ok := payload["last_copied_at"].(string); ok { item.LastCopiedAt = v } if v, ok := payload["active_task_id"].(string); ok { item.ActiveTaskID = v } } resp = append(resp, item) } jsonOK(w, map[string]any{"items": resp}) } func (s *Server) handleDiskProbe(w http.ResponseWriter, r *http.Request) { mountPath := r.URL.Query().Get("mount_path") info, err := s.deps.ProbeDisk(mountPath) if err != nil { jsonErr(w, http.StatusBadRequest, err.Error()) return } jsonOK(w, s.diskResponse(info)) } func (s *Server) handleDiskInit(w http.ResponseWriter, r *http.Request) { var req struct { MountPath string `json:"mount_path"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { jsonErr(w, http.StatusBadRequest, "invalid JSON: "+err.Error()) return } info, err := s.deps.ProbeDisk(req.MountPath) if err != nil { jsonErr(w, http.StatusBadRequest, err.Error()) return } if info.State == disk.DiskAbsent { jsonErr(w, http.StatusUnprocessableEntity, "no disk connected") return } if info.State == disk.DiskKnown { jsonErr(w, http.StatusConflict, "disk already initialized") return } if err := disk.CheckWritable(info.MountPath); err != nil { jsonErr(w, http.StatusUnprocessableEntity, "disk is not writable: "+err.Error()) return } diskID, err := disk.InitDisk(info.MountPath) if err != nil { jsonErr(w, http.StatusInternalServerError, "init disk: "+err.Error()) return } if s.deps.OnDiskInit != nil { s.deps.OnDiskInit(info.MountPath, diskID) } jsonOK(w, map[string]string{"disk_id": diskID}) } func (s *Server) handleGetProfile(w http.ResponseWriter, r *http.Request) { mountPath := config.NormalizeMediaPath(r.URL.Query().Get("mount_path")) if mountPath == "" { jsonErr(w, http.StatusBadRequest, "mount_path is required") return } p, err := disk.LoadProfile(mountPath) if err != nil { jsonErr(w, http.StatusNotFound, "profile not found") return } jsonOK(w, p) } func (s *Server) handlePutProfile(w http.ResponseWriter, r *http.Request) { mountPath := config.NormalizeMediaPath(r.URL.Query().Get("mount_path")) if mountPath == "" { jsonErr(w, http.StatusBadRequest, "mount_path is required") return } var p disk.DiskProfile if err := json.NewDecoder(r.Body).Decode(&p); err != nil { jsonErr(w, http.StatusBadRequest, "invalid JSON: "+err.Error()) return } if err := disk.SaveProfile(mountPath, &p); err != nil { jsonErr(w, http.StatusInternalServerError, "save profile: "+err.Error()) return } // Обновляем информацию о диске в watcher если он там есть if s.deps.Watcher != nil { s.deps.Watcher.ProbeNow() } jsonOK(w, &p) }