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 2, 2025
1 parent 08b905f commit bb91cb7
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 156 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
229 changes: 84 additions & 145 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));
let album_tree_items = crate::files::Library::from_file().songs_by_artist;

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

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 @@ -222,65 +137,89 @@ impl<'a> Library<'a> {
*self.on_select_songs_fn.borrow_mut() = Box::new(cb);
}

pub fn add_songs(&self, songs: Vec<Song>) {
pub fn add_songs(&self, mut songs: Vec<Song>) {
log::debug!(
"Library.add_songs({:?})",
songs.iter().map(|s| s.title.as_str()).collect::<Vec<&str>>()
);
let mut song_tree = self.song_tree.borrow_mut();
let mut song_tree = self.album_tree.borrow_mut();

song_tree.with_nodes_mut(|artist_nodes| {
let mut map: HashMap<String, HashMap<String, Vec<Song>>> = HashMap::new();
for song in songs.drain(..) {
let (Some(artist), Some(album)) = (song.artist.as_ref(), song.album.as_ref()) else {
log::warn!("Missing data for song {song:?}");
continue;
};
map.entry(artist.clone())
.and_modify(|entry| {
entry
.entry(album.clone())
.and_modify(|album_entry| album_entry.push(song.clone()))
.or_insert(vec![song.clone()]);
})
.or_insert({
let mut map = HashMap::new();
map.insert(album.clone(), vec![song]);
map
});
}
for (artist, mut albums) in map.drain() {
let mut node_artist = artist_nodes
.iter_mut()
.find_map(|artist_node| match &artist_node.inner {
AlbumTreeItem::Artist(node_artist) if node_artist.name == artist => Some(artist_node),
_ => None,
});

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);
if let Some(node_artist) = node_artist {
for (album_to_add_name, mut album_to_add_songs) in albums.drain() {
let mut node_album =
node_artist
.children
.iter_mut()
.find_map(|album_node| match &mut album_node.inner {
AlbumTreeItem::Album(ref mut node_album)
if node_album.name == *album_to_add_name =>
{
Some(node_album)
}
_ => None,
});
if let Some(node_album_inner) = node_album {
node_album_inner.songs.append(&mut album_to_add_songs);
} else {
log::info!("Library.add_songs: song already in library {song:#?}");
log::debug!("HOLIX {:?} {album_to_add_name}", node_artist);
node_artist.children.push(TreeNode::new(AlbumTreeItem::Album(Album {
artist: artist.clone(),
name: album_to_add_name,
year: None,
songs: album_to_add_songs,
})))
}
} 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);
} 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],
}],
artist_nodes.push({
let mut new_node = TreeNode::new(AlbumTreeItem::Artist(Artist {
name: artist.clone(),
albums: vec![], // the albums are stored as children nodes
}));
new_node.children = albums
.drain()
.map(|(album_name, album_songs)| {
TreeNode::new(AlbumTreeItem::Album(Album {
artist: artist.clone(),
name: album_name,
year: None,
songs: album_songs,
}))
})
.collect();
new_node
});
}
} else {
log::warn!("Library.add_songs: ignoring song due to missing artist or album in song {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
}
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
8 changes: 7 additions & 1 deletion src/components/tree/tree_node.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use std::collections::VecDeque;

use serde::{Deserialize, Serialize};

use super::TreeNodePath;

#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct TreeNode<T> {
pub inner: T,
pub is_visible: bool,
Expand Down Expand Up @@ -67,6 +69,10 @@ impl<T> TreeNode<T> {
.sum()
}

pub fn children(&self) -> &Vec<Self> {
&self.children
}

pub fn get_child(&self, path: &TreeNodePath) -> Option<&Self> {
assert!(!path.is_empty(), "path cannot be empty");

Expand Down
9 changes: 5 additions & 4 deletions src/files/library.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use std::collections::HashMap;

use serde::{Deserialize, Serialize};

use crate::{structs::Song, toml::read_toml_file_or_default};
use crate::{
components::{AlbumTreeItem, Artist, TreeNode},
toml::read_toml_file_or_default,
};

#[derive(Serialize, Deserialize, Debug, Default)]
pub struct Library {
pub songs_by_artist: HashMap<String, Vec<Song>>,
pub songs_by_artist: Vec<TreeNode<AlbumTreeItem>>,
}

impl Library {
Expand Down

0 comments on commit bb91cb7

Please sign in to comment.