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>
112 lines
3.6 KiB
Go
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)
|
|
}
|