Skip to content

Commit

Permalink
fix: load/save library
Browse files Browse the repository at this point in the history
  • Loading branch information
lautarodragan committed Feb 3, 2025
1 parent 08b905f commit cd742f4
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 199 deletions.
2 changes: 1 addition & 1 deletion src/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ mod tree;
pub use file_browser::{directory_to_songs_and_folders, FileBrowser, FileBrowserSelection};
pub use focus_group::*;
pub use help::Help;
pub use library::Library;
pub use library::*;
pub use list::List;
pub use playlists::Playlists;
pub use queue::Queue;
Expand Down
4 changes: 2 additions & 2 deletions src/components/library.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
mod album_tree_item;
mod keyboard_handler;
mod library;
mod widget;

mod album_tree_item;

pub use album_tree_item::*;
pub use library::*;
8 changes: 5 additions & 3 deletions src/components/library/album_tree_item.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use std::fmt::{Display, Formatter};

use serde::{Deserialize, Serialize};

use crate::structs::Song;

#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Artist {
pub name: String,
pub albums: Vec<Album>,
Expand All @@ -14,15 +16,15 @@ impl Artist {
}
}

#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Album {
pub artist: String,
pub name: String,
pub year: Option<u32>,
pub songs: Vec<Song>,
}

#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum AlbumTreeItem {
Artist(Artist),
Album(Album),
Expand Down
254 changes: 106 additions & 148 deletions src/components/library/library.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,87 +13,36 @@ pub struct Library<'a> {
#[allow(dead_code)]
pub(super) theme: Theme,

pub(super) song_tree: Rc<RefCell<Vec<Artist>>>,

pub(super) song_list: Rc<List<'a, Song>>,
pub(super) album_tree: Rc<RefCell<Tree<'a, AlbumTreeItem>>>,
pub(super) focus_group: FocusGroup<'a>,

pub(super) on_select_songs_fn: Rc<RefCell<Box<dyn FnMut(Vec<&Song>) + 'a>>>,
}

/// TODO: refactor crate::files::Library to store Vec<Artist> and delete this
fn library_file_to_song_tree(songs_by_artist: crate::files::Library) -> Vec<Artist> {
let mut artists: Vec<Artist> = songs_by_artist
.songs_by_artist
.into_iter()
.map(|(name, songs)| {
let mut albums: HashMap<String, Album> = HashMap::new();

for song in songs {
let album_name = song.album.clone().unwrap_or("(album name missing)".to_string());

if let Some(album) = albums.get_mut(album_name.as_str()) {
album.songs.push(song)
} else {
albums.insert(
album_name.clone(),
Album {
artist: name.clone(),
name: album_name,
year: song.year,
songs: vec![song],
},
);
}
}

let mut albums: Vec<Album> = albums.into_values().collect();
albums.sort_unstable_by_key(|a| a.year);

Artist {
name: name.clone(),
albums,
}
})
.collect();

artists.sort_by_key(|a| a.name.clone());
artists
}

impl<'a> Library<'a> {
pub fn new(theme: Theme) -> Self {
let on_select_songs_fn: Rc<RefCell<Box<dyn FnMut(Vec<&Song>) + 'a>>> = Rc::new(RefCell::new(Box::new(|_| {})));

let song_tree = library_file_to_song_tree(crate::files::Library::from_file());

let album_tree_items: Vec<TreeNode<AlbumTreeItem>> = song_tree
.iter()
.cloned()
.map(|mut artist| {
let albums = std::mem::take(&mut artist.albums);
let mut tree_node = TreeNode::new(AlbumTreeItem::Artist(artist));

tree_node.children = albums
.into_iter()
.map(|album| TreeNode::new(AlbumTreeItem::Album(album)))
.collect();
let album_tree_items = crate::files::Library::from_file().songs_by_artist;

tree_node
})
.collect();
let mut song_list = List::new(
theme,
album_tree_items
.first()
.map(|node| {
let AlbumTreeItem::Artist(ref artist) = node.inner else {
panic!("oops");
};
artist.songs()
})
.unwrap_or_default(),
);
let mut album_tree = Tree::new(theme, album_tree_items);

// album_tree.set_is_visible_magic(|item| item.is_artist());
album_tree.set_is_open_all(false);

let mut song_list = List::new(
theme,
song_tree.first().map(|artist| artist.songs()).unwrap_or_default(),
);
let song_tree = Rc::new(RefCell::new(song_tree));

song_list.find_next_item_by_fn({
|songs, i, direction| {
let Some(ref selected_album) = songs[i].album else {
Expand Down Expand Up @@ -152,39 +101,6 @@ impl<'a> Library<'a> {
on_select_songs_fn.borrow_mut()(songs);
}
});
album_tree.on_delete({
let song_tree = song_tree.clone();

move |item, _index| {
log::trace!(target: "::library.album_tree.on_delete", "item deleted {:?}", item);

let mut song_tree = song_tree.borrow_mut();
match item {
AlbumTreeItem::Artist(artist) => {
let Some(i) = song_tree.iter().position(|a| a.name == artist.name) else {
log::error!("Tried to remove an artist that does not exist. {artist:?}");
return;
};
song_tree.remove(i);
// TODO: save changes
// TODO: delete artist's albums (or make list a proper tree view component?)
// let album_tree_items = song_tree_to_album_tree_item_vec(song_tree.clone());
// self.album_tree.set_items(album_tree_items);
}
AlbumTreeItem::Album(album) => {
let Some(i) = song_tree.iter().position(|a| a.name == album.artist) else {
log::error!("Tried to remove an album of an artist that does not exist. {album:?}");
return;
};
song_tree[i].albums.retain(|a| a.name != album.name);
// TODO: save changes
// TODO: mutate artist entry - remove album (or make list a proper tree view component?)
// let album_tree_items = song_tree_to_album_tree_item_vec(song_tree.clone());
// self.album_tree.set_items(album_tree_items);
}
};
}
});
album_tree.on_reorder({
move |parent_path, old_index, new_index| {
log::debug!("album_tree.on_reorder({parent_path}, {old_index}, {new_index})")
Expand All @@ -204,7 +120,6 @@ impl<'a> Library<'a> {

on_select_songs_fn,

song_tree,
song_list,
album_tree,
}
Expand All @@ -227,62 +142,105 @@ impl<'a> Library<'a> {
"Library.add_songs({:?})",
songs.iter().map(|s| s.title.as_str()).collect::<Vec<&str>>()
);
let mut song_tree = self.song_tree.borrow_mut();

for song in songs {
#[rustfmt::skip]
if let Some(ref artist) = song.artist && let Some(ref album) = song.album {
if let Some(artist) = song_tree.iter_mut().find(|i| i.name == *artist) {
if let Some(album) = artist.albums.iter_mut().find(|i| i.name == *album) {
if !album.songs.contains(&song) {
album.songs.push(song);
} else {
log::info!("Library.add_songs: song already in library {song:#?}");
}
} else {
artist.albums.push(Album {
artist: artist.name.clone(),
name: album.clone(),
year: song.year,
songs: vec![song],
})
}
artist.albums.sort_unstable_by_key(|a| a.year);

let songs = song_vec_to_map(songs);

self.album_tree.borrow_mut().with_nodes_mut(|artist_nodes| {
for (artist, albums) in songs.into_iter() {
if let Some(artist_node) = find_artist_node(artist_nodes, artist.as_str()) {
add_album_nodes(artist_node, artist, albums);
} else {
song_tree.push(Artist {
name: artist.to_string(),
albums: vec![Album {
artist: artist.to_string(),
name: album.to_string(),
year: song.year,
songs: vec![song],
}],
});
add_artist_node(artist_nodes, artist, albums);
}
}

// TODO: save changes
});
}
}

fn song_vec_to_map(songs: Vec<Song>) -> HashMap<String, HashMap<String, Vec<Song>>> {
let mut artist_album_map: HashMap<String, HashMap<String, Vec<Song>>> = HashMap::new();

for song in songs.into_iter() {
let (Some(artist), Some(album)) = (song.artist.clone(), song.album.clone()) else {
log::warn!("Missing data for song {song:?}. Cannot add to library.");
continue;
};
if let Some(artist_entry) = artist_album_map.get_mut(artist.as_str()) {
if let Some(album_entry) = artist_entry.get_mut(album.as_str()) {
album_entry.push(song);
} else {
log::warn!("Library.add_songs: ignoring song due to missing artist or album in song {song:#?}");
artist_entry.insert(album, vec![song]);
}
} else {
artist_album_map.insert(artist, HashMap::from([(album, vec![song])]));
}
}

// let album_tree_items = song_tree_to_album_tree_item_vec(song_tree.clone()); // TODO: optimize. this is extremely wasteful!
let album_tree_items: Vec<TreeNode<AlbumTreeItem>> = song_tree
.iter()
.cloned()
.map(|mut artist| {
let albums = std::mem::take(&mut artist.albums);
let mut tree_node = TreeNode::new(AlbumTreeItem::Artist(artist));

tree_node.children = albums
.into_iter()
.map(|album| TreeNode::new(AlbumTreeItem::Album(album)))
.collect();

tree_node
})
.collect();
self.album_tree.borrow_mut().set_items(album_tree_items);

// TODO: save changes
artist_album_map
}

fn find_artist_node<'a>(
artist_nodes: &'a mut [TreeNode<AlbumTreeItem>],
artist_name: &str,
) -> Option<&'a mut TreeNode<AlbumTreeItem>> {
artist_nodes
.iter_mut()
.find_map(|artist_node| match &artist_node.inner {
AlbumTreeItem::Artist(node_artist) if node_artist.name == artist_name => Some(artist_node),
_ => None,
})
}

fn add_artist_node(
artist_nodes: &mut Vec<TreeNode<AlbumTreeItem>>,
artist: String,
albums: HashMap<String, Vec<Song>>,
) {
artist_nodes.push({
TreeNode::new_with_children(
AlbumTreeItem::Artist(Artist {
name: artist.clone(),
albums: vec![], // the albums are stored as children nodes
}),
albums
.into_iter()
.map(|(album_name, album_songs)| {
TreeNode::new(AlbumTreeItem::Album(Album {
artist: artist.clone(),
name: album_name,
year: album_songs.first().and_then(|s| s.year),
songs: album_songs,
}))
})
.collect(),
)
});
}

fn find_album<'a>(artist_node: &'a mut TreeNode<AlbumTreeItem>, album_name: &str) -> Option<&'a mut Album> {
artist_node
.children
.iter_mut()
.find_map(|album_node| match &mut album_node.inner {
AlbumTreeItem::Album(album) if album.name == album_name => Some(album),
_ => None,
})
}

fn add_album_nodes(artist_node: &mut TreeNode<AlbumTreeItem>, artist_name: String, albums: HashMap<String, Vec<Song>>) {
for (album_name, songs) in albums.into_iter() {
if let Some(album) = find_album(artist_node, album_name.as_str()) {
album.songs.extend(songs);
} else {
artist_node.children.push(TreeNode::new(AlbumTreeItem::Album(Album {
artist: artist_name.clone(),
name: album_name,
year: songs.iter().find_map(|song| song.year),
songs,
})))
}
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/components/tree/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ where
}
}

pub fn with_nodes_mut(&mut self, cb: impl FnOnce(&mut Vec<TreeNode<T>>)) {
let mut items = self.items.borrow_mut();
cb(&mut *items)
}

#[allow(unused)]
pub fn set_auto_select_next(&self, v: bool) {
self.auto_select_next.set(v)
Expand Down
Loading

0 comments on commit cd742f4

Please sign in to comment.