Files
MusicServer/internal/server/sync_handler.go
T
Sansan a705d7b476 Store each client's play queue in the database
The queue lived in memory and was shared by all clients, so a restart or a
finished sync cleared it. Every client now has its own queue in the new
queue and queue_song tables (added to the undeployed migration 000006).

The music endpoints return the queue ID in the X-Queue-Id header and expect
it back. A request without an ID, or with an ended one, starts a new queue.
/music/reset ends the queue and returns a new ID, and a sync ends all
running queues. Ended queues are kept as history.

The deprecated legacy routes without the /api/v1 prefix are removed.
2026-10-07 20:03:03 +02:00

86 lines
2.8 KiB
Go

package server
import (
"context"
"net/http"
"reflect"
"music-server/internal/backend"
"music-server/internal/logging"
"github.com/danielgtaylor/huma/v2"
)
func (s *Server) registerSync(a huma.API) {
registry := a.OpenAPI().Components.Schemas
progress := huma.Operation{
OperationID: "get-sync-progress",
Method: http.MethodGet,
Path: "/sync/progress",
Summary: "Sync progress or result",
Description: "While a sync is running, returns its progress. Otherwise returns the result of the last sync.",
Tags: []string{"sync"},
Responses: map[string]*huma.Response{
"200": {
Description: "Progress of the running sync, or the result of the last sync",
Content: map[string]*huma.MediaType{"application/json": {Schema: &huma.Schema{OneOf: []*huma.Schema{
registry.Schema(reflect.TypeFor[backend.ProgressResponse](), true, ""),
registry.Schema(reflect.TypeFor[backend.SyncResponse](), true, ""),
}}}},
},
},
}
register(s, a, route{op: progress},
func(ctx context.Context, _ *struct{}) (*jsonBody[any], error) {
if s.sync.IsSyncing() {
return ok[any](s.sync.Progress(), nil)
}
return ok[any](s.sync.Result(), nil)
})
start := func(id, path, summary, description string, full bool) {
register(s, a, route{
op: huma.Operation{
OperationID: id,
Method: http.MethodPost,
Path: path,
Summary: summary,
Description: description,
Tags: []string{"sync"},
Errors: []int{http.StatusLocked},
},
}, func(ctx context.Context, _ *struct{}) (*jsonBody[string], error) {
message := "Start syncing soundtracks"
if full {
message += " full"
}
if !s.sync.Start(full) {
return nil, huma.Error423Locked("Syncing is in progress")
}
logging.GetLogger().Info(message)
return ok(message, nil)
})
}
start("start-sync", "/sync", "Sync changed soundtracks",
"Starts syncing the music folder in the background, skipping soundtracks that have not changed. "+
"Poll /sync/progress for the result.", false)
start("start-full-sync", "/sync/full", "Sync all soundtracks",
"Starts a full sync in the background that re-reads every soundtrack and removes songs whose files are gone. "+
"Poll /sync/progress for the result.", true)
reset := huma.Operation{
OperationID: "reset-library",
Method: http.MethodPost,
Path: "/sync/reset",
Summary: "Delete all soundtracks and songs",
Description: "For debugging only.",
Tags: []string{"sync"},
Hidden: true,
}
register(s, a, route{op: reset, locked: true},
func(ctx context.Context, _ *struct{}) (*jsonBody[string], error) {
logging.GetLogger().Info("Resetting soundtracks database")
return ok("Soundtracks and songs are deleted from the database", s.sync.ResetDB(ctx))
})
}