Files
minstrel/internal/api/admin_library_folders.go
T
bvandeusenandClaude Opus 5.5 08a2c06b85 docs: api comments say what the endpoints do, not how they came to be (#6277)
Rewrites the internal/api comment blocks to rule 32: task, milestone and
rule ids gone, the stories behind them restated as the constraint they
left, stale references corrected (the stream secret is wired from config,
the timezone handler already refreshes the scheduler, the Android app is
the native client, the design docs cited are not in the repo). Also
rewraps comment lines over 80 columns left by the earlier batches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 12:23:58 -04:00

112 lines
3.6 KiB
Go

package api
import (
"context"
"errors"
"net/http"
"os"
"git.fabledsword.com/bvandeusen/minstrel/internal/apierror"
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
"git.fabledsword.com/bvandeusen/minstrel/internal/library"
)
// The library's folders: where the scan, the watcher and the import
// look for music, set here rather than only by MINSTREL_LIBRARY_SCAN_PATHS.
type libraryFolderView struct {
Path string `json:"path"`
// Present is whether the server can see the folder right now; a folder
// whose mount went away reads false here before anything else notices.
Present bool `json:"present"`
Tracks int64 `json:"tracks"`
}
type libraryFoldersView struct {
Folders []libraryFolderView `json:"folders"`
// Saved is false while the folders still come from
// MINSTREL_LIBRARY_SCAN_PATHS.
Saved bool `json:"saved"`
// Moved is how many tracks a save moved to a new folder.
Moved int64 `json:"moved,omitempty"`
}
func (h *handlers) libraryFolders(ctx context.Context) (libraryFoldersView, error) {
q := dbq.New(h.pool)
row, err := q.GetLibrarySettings(ctx)
if err != nil {
return libraryFoldersView{}, err
}
v := libraryFoldersView{Folders: []libraryFolderView{}, Saved: len(row.ScanPaths) > 0}
for _, root := range h.scanner.Roots() {
n, err := q.CountTracksUnder(ctx, root)
if err != nil {
return libraryFoldersView{}, err
}
info, statErr := os.Stat(root)
v.Folders = append(v.Folders, libraryFolderView{Path: root, Present: statErr == nil && info.IsDir(), Tracks: n})
}
return v, nil
}
// handleGetLibraryFolders implements GET /api/admin/library/folders.
func (h *handlers) handleGetLibraryFolders(w http.ResponseWriter, r *http.Request) {
v, err := h.libraryFolders(r.Context())
if err != nil {
writeErrWithLog(w, h.logger, "admin library folders: get", apierror.Internal(err))
return
}
writeJSON(w, http.StatusOK, v)
}
type putLibraryFoldersBody struct {
Folders []struct {
Path string `json:"path"`
// Was is the folder this one replaces, whose files are now at Path;
// "" is a folder added.
Was string `json:"was"`
} `json:"folders"`
}
// handlePutLibraryFolders implements PUT /api/admin/library/folders.
//
// The whole list. A folder whose path changed moves its tracks with it, so
// their plays, likes and playlists survive; one left out is no longer read,
// and its tracks are marked missing by the next scan. After a save a scan
// starts, to pick up anything the old folders did not hold.
func (h *handlers) handlePutLibraryFolders(w http.ResponseWriter, r *http.Request) {
var body putLibraryFoldersBody
if !decodeBody(w, r, &body) {
return
}
if h.scanner == nil {
writeErr(w, apierror.Internal(errors.New("no scanner")))
return
}
changes := make([]library.RootChange, 0, len(body.Folders))
for _, f := range body.Folders {
changes = append(changes, library.RootChange{Path: f.Path, Was: f.Was})
}
res, err := h.scanner.SetRoots(r.Context(), changes)
if err != nil {
var re *library.RootsError
if errors.As(err, &re) {
writeErr(w, apierror.BadRequest("bad_folders", re.Error()))
return
}
writeErrWithLog(w, h.logger, "admin library folders: put", apierror.Internal(err))
return
}
if _, _, err := library.TryStartScan(context.Background(), h.pool, h.scanner, h.coverart,
h.logger.With("source", "library_folders"), h.scanCfg); err != nil {
h.logger.Warn("admin library folders: scan after save", "err", err)
}
v, err := h.libraryFolders(r.Context())
if err != nil {
writeErrWithLog(w, h.logger, "admin library folders: get", apierror.Internal(err))
return
}
v.Moved = res.Tracks
writeJSON(w, http.StatusOK, v)
}