[READ-ONLY] Mirror of https://github.com/probablykasper/ferrum. Music library app for Mac, Linux and Windows ferrum.kasper.space
electron linux macos music music-library music-player napi windows
0

Configure Feed

Select the types of activity you want to include in your feed.

Add playlist item IDs

Kasper (Sep 26, 2024, 3:20 AM +0200) b547d2cd 6ba21f66

+81 -11
+1 -1
ferrum-addon/addon.d.ts
··· 82 82 originalId?: string 83 83 dateImported?: MsSinceUnixEpoch 84 84 dateCreated?: MsSinceUnixEpoch 85 - tracks: Array<TrackID> 85 + tracks: Array<PlaylistItemId> 86 86 } 87 87 export interface Folder { 88 88 id: TrackListID
+4 -2
src-native/itunes_import.rs
··· 1 1 use crate::data_js::get_data; 2 2 use crate::get_now_timestamp; 3 - use crate::library_types::{CountObject, Folder, Library, Playlist, Track, TrackList}; 3 + use crate::library_types::{ 4 + new_item_ids_from_track_ids, CountObject, Folder, Library, Playlist, Track, TrackList, 5 + }; 4 6 use crate::tracks::generate_filename; 5 7 use crate::tracks::import::{read_file_metadata, FileType}; 6 8 use lofty::file::{AudioFile, TaggedFileExt}; ··· 573 575 originalId: Some(xml_playlist.playlist_persistent_id.clone()), 574 576 dateImported: Some(start_time), 575 577 dateCreated: None, 576 - tracks: track_ids, 578 + tracks: new_item_ids_from_track_ids(&track_ids), 577 579 }); 578 580 // immediately insert into library so new generated ids are unique 579 581 library.trackLists.insert(id.clone(), tracklist);
+59 -1
src-native/library_types.rs
··· 5 5 use nanoid::nanoid; 6 6 use serde::{Deserialize, Serialize}; 7 7 use std::borrow::Cow; 8 + use std::sync::RwLock; 8 9 9 10 #[derive(Serialize, Deserialize, Clone, Debug)] 10 11 #[serde(deny_unknown_fields)] ··· 280 281 *value 281 282 } 282 283 284 + // These are used to give each playlist entry an ID. This is for example helpful to keep track of a user's selection. These IDs are unique across the entire library, so that it works for folders folders. 285 + type ItemId = u32; 286 + pub static PLAYLIST_TRACK_ID_MAP: RwLock<Vec<String>> = RwLock::new(Vec::new()); 287 + 288 + pub fn new_item_ids_from_track_ids(track_ids: &[TrackID]) -> Vec<ItemId> { 289 + let mut playlist_track_id_map = PLAYLIST_TRACK_ID_MAP.write().unwrap(); 290 + let playlist_track_ids: Vec<u32> = track_ids 291 + .iter() 292 + .map(|track_id| { 293 + let new_index = playlist_track_id_map.len(); 294 + playlist_track_id_map.push(track_id.clone()); 295 + new_index as u32 296 + }) 297 + .collect(); 298 + assert_eq!(playlist_track_ids.len(), track_ids.len()); 299 + assert!(playlist_track_id_map.len() < u32::MAX as usize); 300 + playlist_track_ids 301 + } 302 + 303 + pub fn get_track_ids_from_item_ids(playlist_item_ids: &[ItemId]) -> Vec<TrackID> { 304 + let playlist_track_id_map = PLAYLIST_TRACK_ID_MAP.read().unwrap(); 305 + playlist_item_ids 306 + .iter() 307 + .map(|playlist_item_id| playlist_track_id_map[*playlist_item_id as usize].clone()) 308 + .collect() 309 + } 310 + 283 311 #[derive(Serialize, Deserialize, Clone, Debug)] 284 312 #[napi(object)] 285 313 pub struct Playlist { ··· 299 327 pub dateImported: Option<MsSinceUnixEpoch>, 300 328 #[serde(default, skip_serializing_if = "Option::is_none")] 301 329 pub dateCreated: Option<MsSinceUnixEpoch>, 302 - pub tracks: Vec<TrackID>, 330 + #[serde( 331 + deserialize_with = "deserialize_playlist_ids", 332 + serialize_with = "serialize_playlist_ids" 333 + )] 334 + pub tracks: Vec<ItemId>, 335 + } 336 + impl Playlist { 337 + pub fn get_track_ids(&self) -> Vec<TrackID> { 338 + get_track_ids_from_item_ids(&self.tracks) 339 + } 340 + } 341 + 342 + // Deserialize list of strings into list of numbers 343 + fn deserialize_playlist_ids<'de, D>(deserializer: D) -> Result<Vec<ItemId>, D::Error> 344 + where 345 + D: serde::Deserializer<'de>, 346 + { 347 + let track_ids: Vec<String> = serde::Deserialize::deserialize(deserializer)?; 348 + let playlist_track_ids = new_item_ids_from_track_ids(&track_ids); 349 + Ok(playlist_track_ids) 350 + } 351 + 352 + fn serialize_playlist_ids<S>( 353 + playlist_track_ids: &[ItemId], 354 + serializer: S, 355 + ) -> Result<S::Ok, S::Error> 356 + where 357 + S: serde::Serializer, 358 + { 359 + let track_ids = get_track_ids_from_item_ids(playlist_track_ids); 360 + track_ids.serialize(serializer) 303 361 } 304 362 305 363 #[derive(Serialize, Deserialize, Clone, Debug)]
+5 -2
src-native/page.rs
··· 2 2 use crate::data_js::get_data; 3 3 use crate::js::nerr; 4 4 use crate::library::{get_track_field_type, TrackField}; 5 - use crate::library_types::{SpecialTrackListName, Track, TrackID, TrackList}; 5 + use crate::library_types::{ 6 + SpecialTrackListName, Track, TrackID, TrackList, PLAYLIST_TRACK_ID_MAP, 7 + }; 6 8 use crate::sort::sort; 7 9 use crate::{filter, UniResult}; 8 10 use napi::{Env, JsString, JsUndefined, JsUnknown, Result}; ··· 66 68 fn get_tracklist_track_ids(data: &Data, playlist_id: &str) -> UniResult<Vec<TrackID>> { 67 69 match data.library.get_tracklist(playlist_id)? { 68 70 TrackList::Playlist(playlist) => { 71 + let id_map = PLAYLIST_TRACK_ID_MAP.read().unwrap(); 69 72 let ids = playlist 70 73 .tracks 71 74 .iter() 72 - .map(|track_id| track_id.to_string()) 75 + .map(|item_id| id_map[*item_id as usize].clone()) 73 76 .collect(); 74 77 Ok(ids) 75 78 }
+12 -5
src-native/playlists.rs
··· 1 1 use crate::data::Data; 2 2 use crate::data_js::get_data; 3 - use crate::library_types::{Library, SpecialTrackListName, TrackID, TrackList}; 3 + use crate::library_types::{ 4 + new_item_ids_from_track_ids, Library, SpecialTrackListName, TrackID, TrackList, 5 + PLAYLIST_TRACK_ID_MAP, 6 + }; 4 7 use crate::{str_to_option, UniResult}; 5 8 use napi::{Env, JsUnknown, Result}; 6 9 use std::collections::{HashMap, HashSet}; ··· 148 151 149 152 #[napi(js_name = "add_tracks_to_playlist")] 150 153 #[allow(dead_code)] 151 - pub fn add_tracks(playlist_id: String, mut track_ids: Vec<String>, env: Env) -> Result<()> { 154 + pub fn add_tracks(playlist_id: String, track_ids: Vec<String>, env: Env) -> Result<()> { 152 155 let data: &mut Data = get_data(&env)?; 153 156 let playlist = match data.library.get_tracklist_mut(&playlist_id)? { 154 157 TrackList::Playlist(playlist) => playlist, 155 158 TrackList::Folder(_) => throw!("Cannot add track to folder"), 156 159 TrackList::Special(_) => throw!("Cannot add track to special playlist"), 157 160 }; 158 - playlist.tracks.append(&mut track_ids); 161 + let mut new_item_ids = new_item_ids_from_track_ids(&track_ids); 162 + playlist.tracks.append(&mut new_item_ids); 159 163 return Ok(()); 160 164 } 161 165 ··· 168 172 TrackList::Playlist(playlist) => playlist, 169 173 _ => throw!("Cannot check if folder/special contains track"), 170 174 }; 171 - for track in &playlist.tracks { 175 + for track in &playlist.get_track_ids() { 172 176 if track_ids.contains(track) { 173 177 track_ids.remove(track); 174 178 } ··· 210 214 } 211 215 212 216 fn remove_from_all_playlists(library: &mut Library, id: &str) { 217 + let track_id_map = PLAYLIST_TRACK_ID_MAP.read().unwrap(); 213 218 for (_, tracklist) in &mut library.trackLists { 214 219 let playlist = match tracklist { 215 220 TrackList::Playlist(playlist) => playlist, 216 221 _ => continue, 217 222 }; 218 - playlist.tracks.retain(|current_id| current_id != id); 223 + playlist 224 + .tracks 225 + .retain(|current_id| track_id_map[*current_id as usize] != id); 219 226 } 220 227 } 221 228