release / govulncheck (push) Successful in 15s
release / web (push) Successful in 1m20s
release / go (push) Failing after 1m37s
release / integration (push) Failing after 4m56s
release / android (push) Successful in 5m1s
release / Build signed APK (releases and dev) (push) Successful in 5m13s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
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 (#6129): 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)
|
|
}
|