feat(locallibrary): Settings > Local Library — folder management (Slice A)

Port the Tauri folder-management surface to a new Settings > Local Library
sub-section (index 4); the browse-view gear routes here and pre-selects it.

UI:
- LocalLibrarySettings.slint: folder list (icon, display name, disabled/
  unavailable badges, multi-select checkboxes) + toolbar (scan-all/add/edit/
  remove icon buttons) + filter + Maintenance (cleanup missing) + two-step
  Danger Zone clear. ADR-008: plain bordered icon buttons + square badges.
- LibFolderEditModal.slint: alias, enabled, network override + fs-type,
  change-path, accessibility status, last-scanned, scan-this-folder. Mounted
  at the AppShell root (overlays the window, ADR-009 by mount order).
- SettingsView gains the section-4 sub-nav + panel; app.slint re-exports the
  new globals so the Rust layer can populate them.

Controller (local_library_settings.rs): load/add/remove/edit/save/change-path,
cleanup-missing (stats outside the DB lock), two-step clear, filter, select.
All DB access via qbz-library through library_db::with_db on spawn_blocking;
native dir picker + confirms via rfd. Selection preserved across reloads;
network folders get an async 6s-timeout accessibility check (exists() fallback
on timeout, mirroring Tauri).

Core (qbz-library): network_fs_label() for fs-type parity; separator-safe
delete_tracks_in_folder_prefixed() + remove_folder_with_tracks() (fixes the
prefix-collision bug without changing the legacy fn). Scan engine lands in
Slice B.

cc
This commit is contained in:
vicrodh committed 2026-05-29 13:07:19 -06:00
1 parent 5986779d85
commit 3826298f77
11 files changed
+1527 -6

No files matched your search

+67
View File
@@ -508,6 +508,73 @@ export global LocalLibraryActions {
callback artists-select(string /* artist name */);
}
// ============================ Local Library management ====================
// The folder list + scan + maintenance + danger zone live under
// Settings > Local Library (NOT the browse view). One row per registered
// library folder; `Option<T>` maps to a "" / 0 sentinel + a Rust-computed
// label where the raw value is ambiguous.
// One registered library folder. Mirrors `qbz_library::LibraryFolder`
// (8 DB columns) + a UI-owned `selected` checkbox + Rust-computed labels.
export struct LibraryFolderItem {
id: int, // i64 folder id (small)
path: string, // absolute path
alias: string, // "" when none
display-name: string, // alias if non-empty else path (Rust-computed)
enabled: bool,
is-network: bool,
network-fs-type: string, // "" when none (e.g. "nfs","cifs")
user-override-network: bool,
last-scan: int, // unix seconds; 0 = never
last-scan-label: string, // "Never" / localized date (Rust-computed)
accessible: bool, // from the accessibility check; true until resolved
selected: bool, // UI multi-select state
}
// The folder list + maintenance + danger-zone state (Settings > Local Library).
export global LibraryFoldersState {
in property <[LibraryFolderItem]> folders: [];
in property <bool> loading: false;
in-out property <string> filter: ""; // folder search (alias??path substring)
in property <int> selected-count: 0; // drives Edit(==1) / Remove(>0) enablement
in property <bool> cleaning-missing: false;
in property <string> cleanup-status: ""; // "Removed N of M" / "" (auto-clears, Rust-side)
in property <bool> clearing-library: false;
}
// Folder-settings modal state (separate from the playlist FolderEditState).
export global LibFolderEditState {
in-out property <bool> open: false;
in property <int> folder-id: 0;
in-out property <string> alias: "";
in property <string> path: ""; // current path (updated after change-path)
in-out property <bool> enabled: true;
in-out property <bool> is-network: false;
in-out property <bool> user-override-network: false;
// QbzSelect index: 0 auto,1 cifs,2 nfs,3 sshfs,4 rclone,5 webdav,6 glusterfs,7 ceph,8 other.
in-out property <int> fs-type-index: 0;
in property <bool> accessible: true;
in property <bool> checking-accessible: false;
in property <string> last-scan-label: ""; // "Never" / date
}
export global LibraryManageActions {
callback load(); // (re)load the folder list (panel init)
callback add-folder(); // native dir picker (Rust)
callback remove-folders(); // bulk-remove selected (confirm)
callback toggle-folder-select(int /* id */);
callback edit-folder(int /* id */); // open the settings modal
callback save-folder-settings(int /* id */, string /* alias */, bool /* enabled */,
bool /* is-network */, string /* fs-type */, bool /* user-override */);
callback change-folder-path(int /* id */); // picker -> update path
callback scan-all();
callback scan-folder(int /* id */);
callback stop-scan();
callback cleanup-missing();
callback clear-library(); // two-step confirm
callback set-filter(string /* query */);
}
// Global toast state, driven by crate::toast. The Toast overlay (mounted
// at the app root) renders whatever is here. The Rust layer owns timing
// (show + auto-hide); the UI only sets `visible = false` on the close