feat(library): exclude chosen folders from music browsing
Replaces `src/lib/utils/podcastFilter.ts` — a shipped personal workaround that dropped any item whose name, album, album artist or artist was literally "Podcasts" — with a real user setting applied in Rust. The old filter was wrong twice over: it hardcoded one user's folder layout keyed on an English literal, and it put a domain rule (what a query should return) in the presentation layer. It slipped past `check:boundary` only because it matched on names rather than on an item-type array. - `repository::exclusions` owns the rule and the process-wide id set, the same shape as `online::STREAMING_QUALITY` so it survives a repository being rebuilt on re-login. - `HybridRepository` applies it where the cache and server legs of every cache-first query converge (`parallel_race` / `race_with_refresh`), plus the bespoke `get_items` path and the server-only reads. Filtering before the "has content" check is what makes a cache page of nothing but hidden items fall through to the server. - Exclusion is by stable item id, never by name, and matches an item's own id or any container link it carries (parent, album, library, series, season, artist). - A direct `get_item` lookup and the Downloads surface are deliberately unfiltered: hiding those would break playback and file management of anything inside a hidden folder. - `LibrarySettings` persists to `app_settings` and is restored in the setup hook, alongside the streaming-quality cap. Default is an empty list — nobody inherits the old "Podcasts" behaviour. - New commands `library_get_settings`, `library_set_settings` and `library_get_exclusion_candidates`; the candidates read goes through `get_items_unfiltered` so an already-hidden folder still appears in the picker and the setting can be undone. - Settings page gains a "Hidden Folders" section that renders the backend's candidate list and sends back ticked ids; it decides nothing. TRACES: UR-076 | DR-209 | UT-203
This commit is contained in:
@@ -320,6 +320,52 @@ impl VideoSettings {
|
||||
}
|
||||
}
|
||||
|
||||
/// Library browsing preferences.
|
||||
///
|
||||
/// Currently a single list: the folders (or whole libraries) the user has asked
|
||||
/// to keep out of browsing. It is a *list of ids*, never names — names are
|
||||
/// unstable, locale-dependent and non-unique, and the hardcoded name filter this
|
||||
/// setting replaced broke on exactly that. What the ids then hide is decided in
|
||||
/// `repository::exclusions`; this struct is only how the choice is carried and
|
||||
/// persisted.
|
||||
///
|
||||
/// The default is an empty list: nobody inherits another user's folder layout.
|
||||
///
|
||||
/// TRACES: UR-076 | DR-209
|
||||
#[derive(specta::Type, Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LibrarySettings {
|
||||
/// Stable item ids of the folders/libraries hidden from browsing.
|
||||
///
|
||||
/// `#[serde(default)]` so settings JSON persisted before this field existed
|
||||
/// loads as the previous behaviour (nothing hidden).
|
||||
#[serde(default)]
|
||||
pub excluded_item_ids: Vec<String>,
|
||||
}
|
||||
|
||||
impl LibrarySettings {
|
||||
/// Drop blanks and duplicates from the id list.
|
||||
///
|
||||
/// Applied on the way in from IPC and on the way out of the database, so a
|
||||
/// hand-edited or half-written value cannot make the list grow without bound
|
||||
/// or carry an empty id (which would match nothing but still be shown as a
|
||||
/// selection in the picker).
|
||||
///
|
||||
/// TRACES: UR-076 | DR-209
|
||||
pub fn sanitised(mut self) -> Self {
|
||||
let mut seen: Vec<String> = Vec::with_capacity(self.excluded_item_ids.len());
|
||||
for id in self.excluded_item_ids.drain(..) {
|
||||
let id = id.trim().to_string();
|
||||
if id.is_empty() || seen.contains(&id) {
|
||||
continue;
|
||||
}
|
||||
seen.push(id);
|
||||
}
|
||||
self.excluded_item_ids = seen;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialise `AudioSettings` into the JSON payload handed to the Android player
|
||||
/// over JNI.
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user