From 68d2bbd64fdada712d1f49eaf8bd6455f13bbe8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Andersson?= Date: Sun, 22 Nov 2020 17:02:33 +0100 Subject: [PATCH] Add support for sorting playlist tracks (#328) * Add support for sorting playlists * Update string * formatting --- README.md | 12 ++++++++++ src/command.rs | 52 ++++++++++++++++++++++++++++++++++++++++++ src/playlist.rs | 4 +++- src/ui/listview.rs | 56 +++++++++++++++++++++++++++++++++++++++++++++- src/ui/playlist.rs | 5 +++++ 5 files changed, 127 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 195f71e..8021808 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,18 @@ are supported: * `clear`: Clear playlist * `share [current | selected]`: Copies a sharable URL of either the selected item or the currernt song to the system clipboard * `newplaylist `: Create new playlist with name `` +* `sort `: Sort a playlist by `` in direction `` + + Supported `` are: + * title + * album + * artist + * duration + * added + + Supported `` are: + * a | asc | ascending + * d | desc | descending The screens can be opened with `queue`, `search`, `playlists` and `log`, whereas `search` can be supplied with a search term that will be entered after opening diff --git a/src/command.rs b/src/command.rs index e51324a..8ff0b99 100644 --- a/src/command.rs +++ b/src/command.rs @@ -42,6 +42,23 @@ impl Default for MoveAmount { } } +#[derive(Display, Clone, Serialize, Deserialize, Debug)] +#[strum(serialize_all = "lowercase")] +pub enum SortKey { + Title, + Duration, + Artist, + Album, + Added, +} + +#[derive(Display, Clone, Serialize, Deserialize, Debug)] +#[strum(serialize_all = "lowercase")] +pub enum SortDirection { + Ascending, + Descending, +} + #[derive(Display, Clone, Serialize, Deserialize, Debug)] #[strum(serialize_all = "lowercase")] pub enum JumpMode { @@ -116,6 +133,7 @@ pub enum Command { Noop, Insert(Option), NewPlaylist(String), + Sort(SortKey, SortDirection), } impl fmt::Display for Command { @@ -173,6 +191,7 @@ impl fmt::Display for Command { Command::ReloadConfig => "reload".to_string(), Command::Insert(_) => "insert".to_string(), Command::NewPlaylist(name) => format!("new playlist {}", name), + Command::Sort(key, direction) => format!("sort {} {}", key, direction), }; write!(f, "{}", repr) } @@ -369,6 +388,39 @@ pub fn parse(input: &str) -> Option { None } } + "sort" => { + if !args.is_empty() { + let sort_key = args.get(0).and_then(|key| match *key { + "title" => Some(SortKey::Title), + "duration" => Some(SortKey::Duration), + "album" => Some(SortKey::Album), + "added" => Some(SortKey::Added), + "artist" => Some(SortKey::Artist), + _ => None, + }); + + if sort_key.is_none() { + return None; + } + + let sort_direction = args + .get(1) + .and_then(|direction| match *direction { + "a" => Some(SortDirection::Ascending), + "asc" => Some(SortDirection::Ascending), + "ascending" => Some(SortDirection::Ascending), + "d" => Some(SortDirection::Descending), + "desc" => Some(SortDirection::Descending), + "descending" => Some(SortDirection::Descending), + _ => Some(SortDirection::Ascending), + }) + .unwrap_or(SortDirection::Ascending); + + Some(Command::Sort(sort_key.unwrap(), sort_direction)) + } else { + None + } + } "noop" => Some(Command::Noop), _ => None, } diff --git a/src/playlist.rs b/src/playlist.rs index 9f029e2..04477f7 100644 --- a/src/playlist.rs +++ b/src/playlist.rs @@ -37,7 +37,9 @@ impl Playlist { while let Some(ref tracks) = tracks_result.clone() { for listtrack in &tracks.items { if let Some(track) = &listtrack.track { - collected_tracks.push(track.into()); + let mut t: Track = track.into(); + t.added_at = Some(listtrack.added_at); + collected_tracks.push(t); } } debug!("got {} tracks", tracks.items.len()); diff --git a/src/ui/listview.rs b/src/ui/listview.rs index ffa3602..7286be3 100644 --- a/src/ui/listview.rs +++ b/src/ui/listview.rs @@ -11,7 +11,9 @@ use unicode_width::UnicodeWidthStr; use crate::album::Album; use crate::artist::Artist; -use crate::command::{Command, GotoMode, JumpMode, MoveAmount, MoveMode, TargetMode}; +use crate::command::{ + Command, GotoMode, JumpMode, MoveAmount, MoveMode, SortDirection, SortKey, TargetMode, +}; use crate::commands::CommandResult; use crate::episode::Episode; use crate::library::Library; @@ -190,6 +192,58 @@ impl ListView { let mut c = self.content.write().unwrap(); c.remove(index); } + + pub fn sort(&self, key: &SortKey, direction: &SortDirection) { + fn compare_artists(a: Vec, b: Vec) -> Ordering { + let sanitize_artists_name = |x: Vec| -> Vec { + x.iter() + .map(|x| { + x.to_lowercase() + .split(' ') + .skip_while(|x| x == &"the") + .collect() + }) + .collect() + }; + + let a = sanitize_artists_name(a); + let b = sanitize_artists_name(b); + + a.cmp(&b) + } + + let mut c = self.content.write().unwrap(); + + c.sort_by(|a, b| match (a.track(), b.track()) { + (Some(a), Some(b)) => match (key, direction) { + (SortKey::Title, SortDirection::Ascending) => { + a.title.to_lowercase().cmp(&b.title.to_lowercase()) + } + (SortKey::Title, SortDirection::Descending) => { + b.title.to_lowercase().cmp(&a.title.to_lowercase()) + } + (SortKey::Duration, SortDirection::Ascending) => a.duration.cmp(&b.duration), + (SortKey::Duration, SortDirection::Descending) => b.duration.cmp(&a.duration), + (SortKey::Album, SortDirection::Ascending) => a + .album + .map(|x| x.to_lowercase()) + .cmp(&b.album.map(|x| x.to_lowercase())), + (SortKey::Album, SortDirection::Descending) => b + .album + .map(|x| x.to_lowercase()) + .cmp(&a.album.map(|x| x.to_lowercase())), + (SortKey::Added, SortDirection::Ascending) => a.added_at.cmp(&b.added_at), + (SortKey::Added, SortDirection::Descending) => b.added_at.cmp(&a.added_at), + (SortKey::Artist, SortDirection::Ascending) => { + compare_artists(a.artists, b.artists) + } + (SortKey::Artist, SortDirection::Descending) => { + compare_artists(b.artists, a.artists) + } + }, + _ => std::cmp::Ordering::Equal, + }) + } } impl View for ListView { diff --git a/src/ui/playlist.rs b/src/ui/playlist.rs index fa28f40..fefff6e 100644 --- a/src/ui/playlist.rs +++ b/src/ui/playlist.rs @@ -72,6 +72,11 @@ impl ViewExt for PlaylistView { return Ok(CommandResult::Consumed(None)); } + if let Command::Sort(key, direction) = cmd { + self.list.sort(key, direction); + return Ok(CommandResult::Consumed(None)) + } + self.list.on_command(s, cmd) } }