aede_core/playlist/
folders.rs1use super::Style;
3use crate::model::{Catalog, Id};
4use std::collections::BTreeMap;
5use std::path::PathBuf;
6fn in_scope(path: &str, scope: &[String]) -> bool {
7 scope.is_empty() || scope.iter().any(|root| crate::text::is_under(path, root))
8}
9fn lines(text: &str) -> usize {
10 text.lines()
11 .filter(|line| !line.is_empty() && !line.starts_with('#'))
12 .count()
13}
14pub fn albums(catalog: &Catalog, scope: &[String]) -> BTreeMap<PathBuf, Vec<Id>> {
16 let mut by_folder: BTreeMap<PathBuf, Vec<Id>> = BTreeMap::new();
17 for release in &catalog.releases {
18 if !in_scope(&release.folder, scope) {
19 continue;
20 }
21 let folder = PathBuf::from(&release.folder);
26 by_folder
27 .entry(folder)
28 .or_default()
29 .extend(&release.track_ids);
30 }
31 by_folder
32}
33
34pub fn render_folders(
36 catalog: &Catalog,
37 folders: BTreeMap<PathBuf, Vec<Id>>,
38 style: Style,
39) -> Result<Vec<(PathBuf, String)>, String> {
40 let mut out = Vec::new();
41 for (folder, mut tracks) in folders {
42 let mut seen = std::collections::BTreeSet::new();
43 tracks.retain(|id| seen.insert(*id));
44 let text = super::try_render(catalog, &tracks, Some(&folder), style)?;
45 if lines(&text) == 0 {
46 continue;
47 }
48 out.push((folder.join(super::file_name(&folder)), text));
49 }
50 Ok(out)
51}
52
53pub fn discographies(catalog: &Catalog, scope: &[String]) -> BTreeMap<PathBuf, Vec<Id>> {
61 let mut by_artist: BTreeMap<Id, Vec<&crate::model::Release>> = BTreeMap::new();
62 for release in &catalog.releases {
63 if let Some(artist) = release.album_artist_id
64 && in_scope(&release.folder, scope)
65 {
66 by_artist.entry(artist).or_default().push(release);
67 }
68 }
69
70 let roots: Vec<&str> = catalog.roots.iter().map(String::as_str).collect();
71 let mut by_folder: BTreeMap<PathBuf, Vec<&crate::model::Release>> = BTreeMap::new();
72 for (_, releases) in by_artist {
73 let Some(folder) = crate::coverart::shared_artist_folder(&releases) else {
74 continue;
75 };
76 let name = folder.to_string_lossy().to_string();
77 if roots.contains(&name.as_str()) || releases.iter().any(|r| r.folder == name) {
81 continue;
82 }
83 by_folder.entry(folder).or_default().extend(releases);
84 }
85 by_folder
86 .into_iter()
87 .map(|(folder, mut releases)| {
88 releases.sort_by(|a, b| {
92 a.year
93 .unwrap_or(0)
94 .cmp(&b.year.unwrap_or(0))
95 .then_with(|| a.title.cmp(&b.title))
96 });
97 let tracks: Vec<Id> = releases.iter().flat_map(|r| r.track_ids.clone()).collect();
98 (folder, tracks)
99 })
100 .collect()
101}
102
103#[cfg(test)]
104#[path = "folders_tests.rs"]
105mod tests;