aede_core/parental/
visibility.rs1use super::remap::{Maps, compact};
3use crate::{
4 model::{Catalog, EntityKind},
5 user::EntityRef,
6};
7use std::collections::BTreeSet;
8
9pub fn allows_reference(visible: &Catalog, token: &str) -> bool {
11 EntityRef::parse_token(token)
12 .filter(|reference| !reference.key.is_empty())
13 .and_then(|reference| reference.resolve(visible))
14 .is_some()
15}
16pub fn filtered_catalog(catalog: &Catalog, blocked_artists: &[String]) -> Result<Catalog, String> {
20 let mut maps = Maps::new(catalog)?;
21 let mut blocked = BTreeSet::new();
22 for token in blocked_artists {
23 let reference = EntityRef::parse_token(token).ok_or("invalid blocked artist reference")?;
24 if reference.kind != EntityKind::Artist || reference.key.is_empty() {
25 return Err("invalid blocked artist reference".into());
26 }
27 if let Some(id) = reference.resolve(catalog) {
28 blocked.insert(id);
29 }
30 }
31 if blocked.is_empty() {
32 return Ok(catalog.clone());
33 }
34 let mut tracks = vec![false; catalog.tracks.len()];
35 let mut releases = catalog
36 .releases
37 .iter()
38 .map(|release| {
39 release
40 .album_artist_id
41 .is_some_and(|id| blocked.contains(&id))
42 })
43 .collect::<Vec<_>>();
44 let mut recordings = vec![false; catalog.recordings.len()];
45 let mut works = vec![false; catalog.works.len()];
46 for credit in &catalog.credits {
47 if !blocked.contains(&credit.artist_id) {
48 continue;
49 }
50 let table = match credit.entity_kind {
51 EntityKind::Track => &mut tracks,
52 EntityKind::Release => &mut releases,
53 EntityKind::Recording => &mut recordings,
54 EntityKind::Work => &mut works,
55 _ => continue,
56 };
57 *table
58 .get_mut(credit.entity_id as usize)
59 .ok_or("invalid catalogue credit in parental projection")? = true;
60 }
61 for recording in &catalog.recordings {
62 if recording
63 .work_ids
64 .iter()
65 .any(|id| works.get(*id as usize).copied().unwrap_or(true))
66 {
67 *recordings
68 .get_mut(recording.id as usize)
69 .ok_or("invalid catalogue recording")? = true;
70 }
71 }
72 for track in &catalog.tracks {
75 let denied = tracks
76 .get(track.id as usize)
77 .copied()
78 .ok_or("invalid catalogue track")?
79 || track
80 .release_id
81 .is_some_and(|id| releases.get(id as usize).copied().unwrap_or(true));
82 if denied {
83 *recordings
84 .get_mut(track.recording_id as usize)
85 .ok_or("invalid catalogue recording")? = true;
86 }
87 }
88 for track in &catalog.tracks {
89 tracks[track.id as usize] = recordings
90 .get(track.recording_id as usize)
91 .copied()
92 .unwrap_or(true);
93 }
94 for track in catalog
95 .tracks
96 .iter()
97 .filter(|track| !tracks[track.id as usize])
98 {
99 maps.tracks.keep(track.id)?;
100 maps.files.keep(track.file_id)?;
101 maps.recordings.keep(track.recording_id)?;
102 if let Some(release) = track.release_id {
103 maps.releases.keep(release)?;
104 }
105 }
106 for recording in catalog
107 .recordings
108 .iter()
109 .filter(|row| maps.recordings.has(row.id))
110 {
111 for id in &recording.work_ids {
112 maps.works.keep(*id)?;
113 }
114 }
115 for release in catalog
116 .releases
117 .iter()
118 .filter(|row| maps.releases.has(row.id))
119 {
120 if let Some(id) = release.album_artist_id {
121 maps.artists.keep(id)?;
122 }
123 if let Some(id) = release.release_group_id {
124 maps.release_groups.keep(id)?;
125 }
126 for id in &release.label_ids {
127 maps.labels.keep(*id)?;
128 }
129 }
130 for credit in &catalog.credits {
131 if !blocked.contains(&credit.artist_id) && maps.kept(credit.entity_kind, credit.entity_id) {
132 maps.artists.keep(credit.artist_id)?;
133 }
134 }
135 for link in &catalog.genre_links {
136 if maps.kept(link.entity_kind, link.entity_id) {
137 maps.genres.keep(link.genre_id)?;
138 }
139 }
140 maps.finish()?;
141 compact(catalog, &maps)
142}
143#[cfg(test)]
144#[path = "visibility_tests.rs"]
145mod tests;