···282282}
283283284284// 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.
285285-type ItemId = u32;
285285+pub type ItemId = u32;
286286pub static PLAYLIST_TRACK_ID_MAP: RwLock<Vec<String>> = RwLock::new(Vec::new());
287287288288pub fn new_item_ids_from_track_ids(track_ids: &[TrackID]) -> Vec<ItemId> {
+73-159
src-native/page.rs
···11use crate::data::Data;
22use crate::data_js::get_data;
33-use crate::js::nerr;
44-use crate::library::{get_track_field_type, TrackField};
33+use crate::filter::filter;
54use crate::library_types::{
66- SpecialTrackListName, Track, TrackID, TrackList, PLAYLIST_TRACK_ID_MAP,
55+ ItemId, Library, SpecialTrackListName, TrackID, TrackList, PLAYLIST_TRACK_ID_MAP,
76};
87use crate::sort::sort;
99-use crate::{filter, UniResult};
1010-use napi::{Env, JsString, JsUndefined, JsUnknown, Result};
88+use crate::UniResult;
99+use napi::{Env, JsUnknown, Result};
1110use std::collections::HashSet;
1212-use std::time::Instant;
13111414-#[napi(js_name = "open_playlist")]
1515-#[allow(dead_code)]
1616-pub fn open_playlist(open_playlist_id: String, view_as: Option<ViewAs>, env: Env) -> Result<()> {
1717- let data: &mut Data = get_data(&env)?;
1818- data.open_playlist(open_playlist_id, view_as)
1919-}
2020-2121-#[napi(js_name = "get_page_track")]
2222-#[allow(dead_code)]
2323-pub fn get_page_track(index: i64, env: Env) -> Result<Track> {
2424- let data: &mut Data = get_data(&env)?;
2525- let page_track_ids = data.get_page_tracks();
2626- let track_id = page_track_ids.get(index as usize).ok_or(nerr!(
2727- "Track index {} not found in open playlist",
2828- index.to_string()
2929- ))?;
3030- let track = data
3131- .library
3232- .tracks
3333- .get(track_id)
3434- .ok_or(nerr("Track ID not found"))?;
3535- return Ok(track.clone());
1212+#[napi(object)]
1313+#[derive(Clone)]
1414+pub struct TracksPageOptions {
1515+ pub playlist_id: String,
1616+ pub sort_key: String,
1717+ pub sort_desc: bool,
1818+ pub filter_query: String,
1919+ pub group_album_tracks: bool,
3620}
37213838-#[napi(js_name = "get_page_track_id")]
3939-#[allow(dead_code)]
4040-pub fn get_page_track_id(index: i64, env: Env) -> Result<JsString> {
4141- let data: &mut Data = get_data(&env)?;
4242- let page_track_ids = data.get_page_tracks();
4343- let track_id = page_track_ids.get(index as usize).ok_or(nerr!(
4444- "Track index {} not found in open playlist",
4545- index.to_string()
4646- ))?;
4747- return env.create_string(track_id);
4848-}
4949-5050-#[napi(js_name = "refresh_page")]
5151-#[allow(dead_code)]
5252-pub fn refresh(env: Env) -> Result<JsUndefined> {
5353- let data: &mut Data = get_data(&env)?;
5454- let sort_key = data.sort_key.clone();
5555- let sort_desc = data.sort_desc;
5656- sort(data, &sort_key, sort_desc)?;
5757- filter::filter(data, data.filter.clone());
5858- return env.get_undefined();
2222+#[napi(object)]
2323+pub struct TracksPage {
2424+ pub playlist_name: String,
2525+ pub playlist_description: Option<String>,
2626+ pub playlist_length: u32,
2727+ pub track_ids: Vec<TrackID>,
5928}
60296161-#[napi(js_name = "get_page_track_ids")]
3030+#[napi(js_name = "get_tracks_page")]
6231#[allow(dead_code)]
6363-pub fn get_page_track_ids(env: Env) -> Result<Vec<TrackID>> {
3232+pub fn get_tracks_page(options: TracksPageOptions, env: Env) -> Result<TracksPage> {
6433 let data: &mut Data = get_data(&env)?;
6565- Ok(data.get_page_tracks().to_owned())
3434+ let tracklist = data.library.get_tracklist(&options.playlist_id)?;
3535+ let track_ids = sort(options.clone(), &data.library)?;
3636+ let tracklist_length = track_ids.len();
3737+ let track_ids = filter(track_ids, options.filter_query, &data.library);
3838+ let track_page = match tracklist {
3939+ TrackList::Playlist(playlist) => TracksPage {
4040+ playlist_name: playlist.name.clone(),
4141+ playlist_description: playlist.description.clone(),
4242+ playlist_length: tracklist_length as u32,
4343+ track_ids,
4444+ },
4545+ TrackList::Folder(folder) => TracksPage {
4646+ playlist_name: folder.name.clone(),
4747+ playlist_description: folder.description.clone(),
4848+ playlist_length: tracklist_length as u32,
4949+ track_ids,
5050+ },
5151+ TrackList::Special(special) => TracksPage {
5252+ playlist_name: special.name.to_string(),
5353+ playlist_description: None,
5454+ playlist_length: tracklist_length as u32,
5555+ track_ids,
5656+ },
5757+ };
5858+ Ok(track_page)
6659}
67606868-fn get_tracklist_track_ids(data: &Data, playlist_id: &str) -> UniResult<Vec<TrackID>> {
6969- match data.library.get_tracklist(playlist_id)? {
6161+pub fn get_tracklist_track_ids(library: &Library, playlist_id: &str) -> UniResult<Vec<TrackID>> {
6262+ match library.get_tracklist(playlist_id)? {
7063 TrackList::Playlist(playlist) => {
7164 let id_map = PLAYLIST_TRACK_ID_MAP.read().unwrap();
7265 let ids = playlist
···7972 TrackList::Folder(folder) => {
8073 let mut ids: HashSet<TrackID> = HashSet::new();
8174 for child in &folder.children {
8282- let child_ids = get_tracklist_track_ids(data, &child)?;
7575+ let child_ids = get_tracklist_track_ids(library, &child)?;
8376 ids.extend(child_ids);
8477 }
8578 Ok(ids.into_iter().collect())
8679 }
8780 TrackList::Special(special) => match special.name {
8881 SpecialTrackListName::Root => {
8989- let track_keys = data.library.tracks.keys();
8282+ let track_keys = library.tracks.keys();
9083 let ids = track_keys.map(|track| track.to_string()).collect();
9184 Ok(ids)
9285 }
9386 },
9487 }
9595-}
9696-pub fn get_track_ids(data: &Data) -> UniResult<Vec<TrackID>> {
9797- let now = Instant::now();
9898- let ids = get_tracklist_track_ids(data, &data.open_playlist_id)?;
9999- println!("Load page tracks: {}ms", now.elapsed().as_millis());
100100- Ok(ids)
10188}
1028910390#[napi]
···122109 pub length: i64,
123110}
124111125125-#[allow(dead_code)]
126126-#[napi(js_name = "get_page_info")]
127127-pub fn get_page_info(env: Env) -> Result<PageInfo> {
128128- let data = get_data(&env)?;
129129- let tracklist = data.library.get_tracklist(&data.open_playlist_id)?;
130130-131131- Ok(PageInfo {
132132- id: data.open_playlist_id.clone(),
133133- view_as: data.view_as,
134134- tracklist: env.to_js_value(tracklist)?,
135135- sort_key: data.sort_key.clone(),
136136- sort_desc: data.sort_desc,
137137- length: data.get_page_tracks().len().try_into().expect("Too long"),
138138- })
139139-}
140140-141141-#[napi(js_name = "set_group_album_tracks")]
142142-#[allow(dead_code)]
143143-pub fn set_group_album_tracks(value: bool, env: Env) -> Result<JsUndefined> {
144144- let data: &mut Data = get_data(&env)?;
145145- data.group_album_tracks = value;
146146- env.get_undefined()
147147-}
148148-149149-#[napi(js_name = "sort")]
150150-#[allow(dead_code)]
151151-pub fn sort_js(sort_key: String, keep_filter: bool, env: Env) -> Result<JsUndefined> {
152152- let data: &mut Data = get_data(&env)?;
153153-154154- let old_sort_key = &data.sort_key;
155155- if &sort_key == old_sort_key {
156156- data.open_playlist_track_ids.reverse();
157157- data.sort_desc = !data.sort_desc;
158158- } else {
159159- let field = get_track_field_type(&sort_key);
160160- let desc = match field {
161161- Some(TrackField::String) => false,
162162- _ => true,
163163- };
164164-165165- sort(data, &sort_key, desc)?;
166166- }
167167-168168- if keep_filter {
169169- filter::filter(data, data.filter.clone());
170170- }
171171- return env.get_undefined();
172172-}
173173-174174-#[napi(object)]
175175-pub struct SelectionInfo {
176176- pub from: u32,
177177- pub to: u32,
178178-}
179179-180112#[napi(js_name = "move_tracks")]
181113#[allow(dead_code)]
182114pub fn move_tracks(
183183- mut indexes_to_move: Vec<u32>,
115115+ playlist_id: String,
116116+ mut item_ids: Vec<ItemId>,
184117 to_index: u32,
185118 env: Env,
186186-) -> Result<SelectionInfo> {
119119+) -> Result<()> {
187120 let data: &mut Data = get_data(&env)?;
188188- indexes_to_move.sort_unstable();
189189- indexes_to_move.dedup();
190190- let tracklist = data
191191- .library
192192- .trackLists
193193- .get_mut(&data.open_playlist_id)
194194- .ok_or(nerr!("Playlist ID not found"))?;
195195- let playlist = match tracklist {
121121+ let playlist = match data.library.get_tracklist_mut(&playlist_id)? {
196122 TrackList::Playlist(playlist) => playlist,
197197- TrackList::Folder(_) => return Err(nerr!("Cannot rearrange tracks in folder")),
198198- TrackList::Special(_) => return Err(nerr!("Cannot rearrange tracks in special playlist")),
123123+ _ => return Err(nerr!("Cannot rearrange tracks in non-playlist")),
199124 };
200200- if data.sort_key != "index" || !data.sort_desc {
201201- return Err(nerr!("Cannot rearrange when custom sorting is used"));
202202- }
203203- if data.filter != "" {
204204- return Err(nerr!("Cannot rearrange when filter is used"));
205205- }
206206- let mut start_ids = Vec::new();
207207- let mut moved_ids = Vec::new();
208208- let mut end_ids = Vec::new();
209125210210- let mut indexes_to_move = indexes_to_move.iter();
211211- let mut next_index = indexes_to_move.next().map(|n| *n as usize);
212212- for i in 0..playlist.tracks.len() {
213213- let id = playlist.tracks.remove(0);
214214- if Some(i) == next_index {
215215- next_index = indexes_to_move.next().map(|n| *n as usize);
216216- moved_ids.push(id);
217217- } else if i < to_index as usize {
218218- start_ids.push(id);
219219- } else {
220220- end_ids.push(id);
221221- }
126126+ let item_ids_set: HashSet<ItemId> = item_ids.iter().cloned().collect();
127127+ assert_eq!(item_ids_set.len(), item_ids.len());
128128+129129+ let playlist_item_ids_set: HashSet<ItemId> = playlist.tracks.iter().cloned().collect();
130130+ for item_id in &item_ids {
131131+ assert!(playlist_item_ids_set.contains(item_id));
222132 }
223223- let new_from = start_ids.len() as u32;
224224- let new_to = new_from + moved_ids.len() as u32 - 1;
225225- start_ids.append(&mut moved_ids);
226226- start_ids.append(&mut end_ids);
227227- playlist.tracks = start_ids;
228228- Ok(SelectionInfo {
229229- from: new_from,
230230- to: new_to,
231231- })
133133+134134+ let mut start_items = playlist.tracks.clone();
135135+ start_items.retain(|item_id| !item_ids_set.contains(item_id));
136136+137137+ let mut end_items = start_items.split_off(to_index as usize);
138138+ end_items.retain(|item_id| !item_ids_set.contains(item_id));
139139+140140+ start_items.append(&mut item_ids);
141141+142142+ start_items.append(&mut end_items);
143143+144144+ playlist.tracks = start_items;
145145+ Ok(())
232146}
+21-59
src-native/playlists.rs
···11use crate::data::Data;
22use crate::data_js::get_data;
33use crate::library_types::{
44- new_item_ids_from_track_ids, Library, SpecialTrackListName, TrackID, TrackList,
55- PLAYLIST_TRACK_ID_MAP,
44+ get_track_ids_from_item_ids, new_item_ids_from_track_ids, ItemId, Library,
55+ SpecialTrackListName, TrackID, TrackList, PLAYLIST_TRACK_ID_MAP,
66};
77use crate::{str_to_option, UniResult};
88use napi::{Env, JsUnknown, Result};
···118118 Ok(())
119119}
120120121121-fn delete_track_list(data: &mut Data, id: String) -> Result<()> {
121121+/// Returns the deleted track lists, including folder children
122122+#[napi(js_name = "delete_track_list")]
123123+#[allow(dead_code)]
124124+pub fn delete_track_list_js(id: String, env: Env) -> Result<()> {
125125+ let data: &mut Data = get_data(&env)?;
122126 let parent_id = data
123127 .library
124128 .get_parent_id(&id)
···135139 for id in &ids {
136140 data.library.trackLists.remove(id);
137141 }
138138- if ids.contains(&data.open_playlist_id) {
139139- data.open_playlist("root".to_string(), None)?;
140140- }
141142 Ok(())
142142-}
143143-144144-/// Returns the deleted track lists, including folder children
145145-#[napi(js_name = "delete_track_list")]
146146-#[allow(dead_code)]
147147-pub fn delete_track_list_js(id: String, env: Env) -> Result<()> {
148148- let data: &mut Data = get_data(&env)?;
149149- delete_track_list(data, id)
150143}
151144152145#[napi(js_name = "add_tracks_to_playlist")]
···181174 Ok(track_ids)
182175}
183176184184-#[napi(js_name = "remove_from_open_playlist")]
177177+#[napi(js_name = "remove_from_playlist")]
185178#[allow(dead_code)]
186186-pub fn remove_from_open(mut indexes_to_remove: Vec<u32>, env: Env) -> Result<()> {
179179+pub fn remove_from_playlist(playlist_id: String, item_ids: Vec<ItemId>, env: Env) -> Result<()> {
187180 let data: &mut Data = get_data(&env)?;
188188- indexes_to_remove.sort_unstable();
189189- indexes_to_remove.dedup();
190190- let playlist = match data.library.get_tracklist_mut(&data.open_playlist_id)? {
181181+ let playlist = match data.library.get_tracklist_mut(&playlist_id)? {
191182 TrackList::Playlist(playlist) => playlist,
192192- TrackList::Folder(_) => throw!("Cannot remove track from folder"),
193193- TrackList::Special(_) => throw!("Cannot remove track from special playlist"),
183183+ _ => throw!("Cannot remove track from non-playlist"),
194184 };
195195- if data.sort_key != "index" || !data.sort_desc {
196196- throw!("Cannot remove track when custom sorting is used");
197197- }
198198- if data.filter != "" {
199199- throw!("Cannot remove track when filter is used");
200200- }
201201- let mut new_list = Vec::new();
202202- let mut indexes_to_remove = indexes_to_remove.iter();
203203- let mut next_index = indexes_to_remove.next().map(|n| *n as usize);
204204- for i in 0..playlist.tracks.len() {
205205- let id = playlist.tracks.remove(0);
206206- if Some(i) == next_index {
207207- next_index = indexes_to_remove.next().map(|n| *n as usize);
208208- } else {
209209- new_list.push(id);
210210- }
211211- }
212212- playlist.tracks = new_list;
185185+ let items_to_remove: HashSet<ItemId> = item_ids.into_iter().collect();
186186+187187+ playlist
188188+ .tracks
189189+ .retain(|item_id| !items_to_remove.contains(item_id));
190190+213191 return Ok(());
214192}
215193···226204 }
227205}
228206229229-fn get_page_ids(data: &mut Data, indexes: Vec<u32>) -> UniResult<Vec<String>> {
230230- let mut ids = Vec::new();
231231- let page_track_ids = data.get_page_tracks();
232232- for index in indexes {
233233- let id = match page_track_ids.get(index as usize) {
234234- Some(id) => id,
235235- None => throw!("Track index not found"),
236236- };
237237- ids.push(id.clone());
238238- }
239239- Ok(ids)
240240-}
241241-242207fn delete_file(path: &PathBuf) -> UniResult<()> {
243208 #[allow(unused_mut)]
244209 let mut trash_context = trash::TrashContext::new();
···252217 }
253218}
254219255255-#[napi(js_name = "delete_tracks_in_open")]
220220+#[napi(js_name = "delete_tracks")]
256221#[allow(dead_code)]
257257-pub fn delete_tracks_in_open(mut indexes_to_delete: Vec<u32>, env: Env) -> Result<()> {
222222+pub fn delete_tracks_with_item_id(item_ids: Vec<ItemId>, env: Env) -> Result<()> {
258223 let data: &mut Data = get_data(&env)?;
259259- indexes_to_delete.sort_unstable();
260260- indexes_to_delete.dedup();
261261- let ids_to_delete = get_page_ids(data, indexes_to_delete)?;
262224 let library = &mut data.library;
263263-264264- for id_to_delete in &ids_to_delete {
225225+ let track_ids = get_track_ids_from_item_ids(&item_ids);
226226+ for id_to_delete in &track_ids {
265227 let file_path = {
266228 let track = library.get_track(id_to_delete)?;
267229 data.paths.tracks_dir.join(&track.file)
+21-35
src-native/sort.rs
···11-use crate::data::Data;
21use crate::library::{get_track_field_type, TrackField};
33-use crate::library_types::{Track, TrackList};
44-use crate::{page, UniResult};
22+use crate::library_types::{Library, Track, TrackID};
33+use crate::page::{get_tracklist_track_ids, TracksPageOptions};
44+use crate::UniResult;
55use alphanumeric_sort::compare_str;
66use std::cmp::Ordering;
77use std::time::Instant;
···8585 }
8686}
87878888-pub fn sort(data: &mut Data, sort_key: &str, desc: bool) -> UniResult<()> {
8888+pub fn sort(options: TracksPageOptions, library: &Library) -> UniResult<Vec<TrackID>> {
8989 let now = Instant::now();
90909191- if sort_key == "index" {
9292- // No need to sort for index. Indexes descend from "first to last"
9393- // instead of "high to low", so it needs to be reversed
9494- let playlist = data
9595- .library
9696- .trackLists
9797- .get(&data.open_playlist_id)
9898- .ok_or("Playlist ID not found")?;
9999- match playlist {
100100- TrackList::Playlist(_) => {
101101- data.open_playlist_track_ids = page::get_track_ids(&data)?;
102102- data.sort_key = sort_key.to_string();
103103- data.sort_desc = true;
104104- println!("Sort: {}ms", now.elapsed().as_millis());
105105- return Ok(());
106106- }
107107- TrackList::Folder(_) | TrackList::Special(_) => return Ok(()),
108108- }
109109- }
9191+ let mut track_ids = get_tracklist_track_ids(library, &options.playlist_id)?;
11092111111- data.open_playlist_track_ids = page::get_track_ids(&data)?;
9393+ if options.sort_key == "index" {
9494+ // No need to sort for index. Note: Indexes descend from "first to last",
9595+ // unlike other numbers which ascend from "high to low"
9696+ println!("Sort: {}ms", now.elapsed().as_millis());
9797+ track_ids.reverse();
9898+ return Ok(track_ids);
9999+ }
112100113113- let tracks = &data.library.tracks;
114114- let field = match get_track_field_type(sort_key) {
101101+ let tracks = &library.tracks;
102102+ let field = match get_track_field_type(&options.sort_key) {
115103 Some(field) => field,
116116- None => throw!("Field type not found for {sort_key}"),
104104+ None => throw!("Field type not found for {}", options.sort_key),
117105 };
118118- let subsort_field = data.group_album_tracks
119119- && match sort_key {
106106+ let subsort_field = options.group_album_tracks
107107+ && match options.sort_key.as_str() {
120108 "dateAdded" | "albumName" | "comments" | "genre" | "year" | "artist" => true,
121109 _ => false,
122110 };
123123- data.open_playlist_track_ids.sort_by(|id_a, id_b| {
111111+ track_ids.sort_by(|id_a, id_b| {
124112 let track_a = tracks.get(id_a).expect("Track ID non-existant (1)");
125113 let track_b = tracks.get(id_b).expect("Track ID non-existant (2)");
126114 if subsort_field
···138126 order => return order,
139127 };
140128 }
141141- let order = compare_track_field(track_a, track_b, sort_key, &field);
142142- return match desc {
129129+ let order = compare_track_field(track_a, track_b, &options.sort_key, &field);
130130+ return match options.sort_desc {
143131 true => order.reverse(),
144132 false => order,
145133 };
146134 });
147147- data.sort_key = sort_key.to_string();
148148- data.sort_desc = desc;
149135 println!("Sort: {}ms", now.elapsed().as_millis());
150150- return Ok(());
136136+ return Ok(track_ids);
151137}
152138153139pub fn compare_track_field(a: &Track, b: &Track, sort_key: &str, field: &TrackField) -> Ordering {