feat(libraries): scope a library scan to one registered folder
POST /api/libraries/:id/scan accepts an optional {"folder_path": "..."}:
the scan then covers exactly that one registered folder's subtree instead
of the whole library. The path is validated against the library's
registered folders — anything else is a 400, never trusted. An omitted
body keeps today's full-library behavior, and coalescing (409 with the
active job) is unchanged.
The LibraryPanel folder rows get a per-folder Scan button: pure htmx
attributes posting to the same scan endpoint, with hxValsFolderScan
emitting plain eval-free JSON for hx-vals alongside the existing
hxValsFolderDelete precedent. Progress surfaces through the existing
active-scan polling; no new client code.
Safety: the scanner's archive-reconciliation guard (previous commit)
skips partially-covered libraries, so a one-folder scan cannot mark the
library's other folders' items missing.
This commit is contained in:
1 parent
6f37be59fc
commit
0f1d0abbb0
6 files changed
+286
-197
No files matched your search
@@ -27,7 +27,12 @@ func (h *Handler) StartBackgroundTasks() {
|
||||
type ScanLibraryRequest struct {
|
||||
FolderPaths []string `json:"folder_paths,omitempty"`
|
||||
LibraryID string `json:"library_id,omitempty"`
|
||||
Force bool `json:"force,omitempty"`
|
||||
// FolderPath (singular) scopes a library scan to ONE of the library's
|
||||
// registered folders (the LibraryPanel per-folder Scan buttons). It
|
||||
// must be a registered folder — validated below, never trusted. The
|
||||
// raw /api/scanner/scan route ignores it (no library attribution).
|
||||
FolderPath string `json:"folder_path,omitempty"`
|
||||
Force bool `json:"force,omitempty"`
|
||||
}
|
||||
|
||||
// ScanLibrary handles POST /api/scanner/scan (now runs in background)
|
||||
@@ -68,9 +73,28 @@ func (h *Handler) ScanLibrary(c *echo.Context) error {
|
||||
return c.JSON(http.StatusNotFound, map[string]string{"error": "library not found or has no folders"})
|
||||
}
|
||||
|
||||
// Extract folder paths
|
||||
for _, folder := range libraryFolders {
|
||||
folderPaths = append(folderPaths, folder.FolderPath)
|
||||
if req.FolderPath != "" {
|
||||
// Per-folder scope (LibraryPanel per-folder Scan buttons):
|
||||
// scan exactly one registered folder's subtree. The archive
|
||||
// reconciliation pass skips partially-covered libraries
|
||||
// (processScanJob → ScanFolders), so the other folders'
|
||||
// items cannot be marked missing by this run.
|
||||
registered := false
|
||||
for _, folder := range libraryFolders {
|
||||
if folder.FolderPath == req.FolderPath {
|
||||
registered = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !registered {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "folder_path is not one of this library's registered folders"})
|
||||
}
|
||||
folderPaths = []string{req.FolderPath}
|
||||
} else {
|
||||
// Extract folder paths
|
||||
for _, folder := range libraryFolders {
|
||||
folderPaths = append(folderPaths, folder.FolderPath)
|
||||
}
|
||||
}
|
||||
|
||||
if len(folderPaths) == 0 {
|
||||
|
||||
Reference in new issue
Block a user