Files
jukebox_maker/internal/api/handlers_copy.go

179 lines
4.6 KiB
Go

package api
import (
"context"
"encoding/json"
"errors"
"io"
"net/http"
"jukebox_maker/internal/config"
"jukebox_maker/internal/copier"
"jukebox_maker/internal/disk"
)
func (s *Server) copyOptions(cfg *config.Config, diskInfo disk.DiskInfo, overwriteMode config.OverwriteMode) copier.Options {
return copier.Options{
DiskID: diskInfo.DiskID,
MountPath: diskInfo.MountPath,
MediaPath: cfg.MediaPath,
DestFolder: cfg.DestFolder,
SourceRules: cfg.Sources,
AllowedExtensions: cfg.EffectiveAllowedExtensions(),
ReserveFreeGB: cfg.ReserveFreeGB,
OverwriteMode: overwriteMode,
FileSelectMode: cfg.FileSelectMode,
}
}
func hasEnabledSources(cfg *config.Config) bool {
for _, src := range cfg.Sources {
if src.Enabled {
return true
}
}
return false
}
func decodeCopyMode(r *http.Request, fallback config.OverwriteMode) (config.OverwriteMode, error) {
var req struct {
Mode string `json:"mode"`
}
if r.Body != nil {
if err := json.NewDecoder(r.Body).Decode(&req); err != nil && !errors.Is(err, io.EOF) {
return "", err
}
}
switch req.Mode {
case "", "add":
return config.OverwriteSkip, nil
case "replace":
return config.OverwriteDelete, nil
default:
return fallback, errors.New("invalid copy mode")
}
}
func (s *Server) handleCopyStart(w http.ResponseWriter, r *http.Request) {
diskID := r.PathValue("diskID")
diskInfo, ok := s.deps.Watcher.DiskByID(diskID)
if !ok || diskInfo.State != disk.DiskKnown {
jsonErr(w, http.StatusUnprocessableEntity, "no initialized disk connected")
return
}
cfg := s.deps.Config
if !hasEnabledSources(cfg) {
jsonErr(w, http.StatusUnprocessableEntity, "no source folders selected")
return
}
overwriteMode, err := decodeCopyMode(r, cfg.OverwriteMode)
if err != nil {
if err.Error() == "invalid copy mode" {
jsonErr(w, http.StatusBadRequest, err.Error())
return
}
jsonErr(w, http.StatusBadRequest, "invalid JSON: "+err.Error())
return
}
reserveBytes := int64(cfg.ReserveFreeGB * 1e9)
if diskInfo.FreeBytes <= reserveBytes {
jsonErr(w, http.StatusUnprocessableEntity, "free space is below reserve threshold")
return
}
opts := s.copyOptions(cfg, diskInfo, overwriteMode)
taskID, err := s.deps.Copier.Start(context.Background(), opts)
if err != nil {
switch err.Error() {
case "copy already running":
jsonErr(w, http.StatusConflict, err.Error())
default:
jsonErr(w, http.StatusUnprocessableEntity, err.Error())
}
return
}
w.WriteHeader(http.StatusAccepted)
jsonOK(w, map[string]string{"task_id": taskID})
}
func (s *Server) handleCopyStartSelected(w http.ResponseWriter, r *http.Request) {
cfg := s.deps.Config
if !hasEnabledSources(cfg) {
jsonErr(w, http.StatusUnprocessableEntity, "no source folders selected")
return
}
var req struct {
MountPath string `json:"mount_path"`
Mode string `json:"mode"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
jsonErr(w, http.StatusBadRequest, "invalid JSON: "+err.Error())
return
}
diskInfo, err := s.deps.ProbeDisk(req.MountPath)
if err != nil {
jsonErr(w, http.StatusBadRequest, err.Error())
return
}
if diskInfo.State != disk.DiskKnown {
jsonErr(w, http.StatusUnprocessableEntity, "selected directory is not an initialized jukebox disk")
return
}
overwriteMode := cfg.OverwriteMode
switch req.Mode {
case "", "add":
overwriteMode = config.OverwriteSkip
case "replace":
overwriteMode = config.OverwriteDelete
default:
jsonErr(w, http.StatusBadRequest, "invalid copy mode")
return
}
reserveBytes := int64(cfg.ReserveFreeGB * 1e9)
if diskInfo.FreeBytes <= reserveBytes {
jsonErr(w, http.StatusUnprocessableEntity, "free space is below reserve threshold")
return
}
if s.deps.OnDiskInit != nil {
s.deps.OnDiskInit(diskInfo.MountPath, diskInfo.DiskID)
}
taskID, err := s.deps.Copier.Start(context.Background(), s.copyOptions(cfg, diskInfo, overwriteMode))
if err != nil {
switch err.Error() {
case "copy already running":
jsonErr(w, http.StatusConflict, err.Error())
default:
jsonErr(w, http.StatusUnprocessableEntity, err.Error())
}
return
}
w.WriteHeader(http.StatusAccepted)
jsonOK(w, map[string]string{"task_id": taskID})
}
func (s *Server) handleCopyCancel(w http.ResponseWriter, r *http.Request) {
diskID := r.PathValue("diskID")
s.deps.Copier.Cancel(diskID)
jsonOK(w, map[string]bool{"ok": true})
}
func (s *Server) handleTaskGet(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
t, ok := s.deps.Tasks.Get(id)
if !ok {
jsonErr(w, http.StatusNotFound, "task not found")
return
}
jsonOK(w, t)
}