From 4c118cffc3f986a8018218fcb02b27de90016345 Mon Sep 17 00:00:00 2001 From: taro Date: Fri, 29 Nov 2024 13:31:35 -0300 Subject: [PATCH] refactor: rustfmt + clippy --- .clippy.toml | 2 + src/app.rs | 146 ++++--- src/auto_update.rs | 11 +- src/bye.rs | 6 +- src/components.rs | 10 +- src/components/file_browser.rs | 6 +- src/components/file_browser/file_browser.rs | 28 +- .../file_browser/file_browser_selection.rs | 119 +++--- .../file_browser/keyboard_handler.rs | 8 +- src/components/file_browser/widget.rs | 38 +- src/components/help/help.rs | 26 +- src/components/library.rs | 2 +- src/components/library/album_tree.rs | 6 +- .../library/album_tree/album_tree_item.rs | 16 +- .../library/album_tree/component.rs | 38 +- .../library/album_tree/keyboard_handler.rs | 82 ++-- src/components/library/album_tree/widget.rs | 25 +- src/components/library/keyboard_handler.rs | 7 +- src/components/library/library.rs | 80 ++-- src/components/library/widget.rs | 8 +- src/components/list.rs | 2 +- src/components/list/component.rs | 49 ++- src/components/list/keyboard_handler.rs | 68 ++-- src/components/list/widget.rs | 22 +- src/components/playlists.rs | 2 +- src/components/playlists/keyboard_handler.rs | 17 +- src/components/playlists/playlists.rs | 27 +- src/components/playlists/widget.rs | 29 +- src/components/queue.rs | 2 +- src/components/queue/queue.rs | 27 +- src/components/queue/widget.rs | 6 +- src/components/rendering_error.rs | 9 +- src/cue/cue_line.rs | 2 +- src/cue/cue_line_node.rs | 20 +- src/cue/cue_sheet.rs | 25 +- src/cue/cue_sheet_item.rs | 19 +- src/files/library.rs | 13 +- src/files/playlists.rs | 11 +- src/main.rs | 30 +- src/mpris.rs | 6 +- src/player.rs | 378 +++++++++--------- src/source.rs | 19 +- src/spawn_terminal.rs | 6 +- src/state.rs | 11 +- src/structs.rs | 12 +- src/structs/action.rs | 42 +- src/structs/jolt.rs | 12 +- src/structs/song.rs | 97 ++--- src/ui/currently_playing.rs | 19 +- src/ui/keyboard_handler.rs | 2 +- src/ui/top_bar.rs | 5 +- 51 files changed, 774 insertions(+), 879 deletions(-) create mode 100644 .clippy.toml diff --git a/.clippy.toml b/.clippy.toml new file mode 100644 index 0000000..a149281 --- /dev/null +++ b/.clippy.toml @@ -0,0 +1,2 @@ +allow-private-module-inception = true +type-complexity-threshold = 700 diff --git a/src/app.rs b/src/app.rs index 173fe0e..66d2438 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,13 +1,12 @@ use std::{ - error::Error, env, + error::Error, path::PathBuf, + rc::Rc, sync::{ - mpsc::Receiver, - Arc, - Mutex, - MutexGuard, atomic::{AtomicBool, AtomicU64, Ordering}, + mpsc::Receiver, + Arc, Mutex, }, thread, thread::JoinHandle, @@ -24,16 +23,15 @@ use ratatui::{ use rodio::OutputStream; use crate::{ + components::{FileBrowser, FileBrowserSelection, Help, Library, Playlists, Queue}, config::Theme, - structs::{Song, Action, Actions, OnAction}, player::Player, state::State, + structs::{Action, Actions, OnAction, OnActionMut, ScreenAction, Song}, term::set_terminal, - ui::{KeyboardHandlerRef, KeyboardHandlerMut, TopBar, Component}, + ui::{Component, KeyboardHandlerMut, KeyboardHandlerRef, TopBar}, Command, - components::{FileBrowser, FileBrowserSelection, Library, Playlists, Queue, Help}, }; -use crate::structs::{OnActionMut, ScreenAction}; pub struct App<'a> { must_quit: bool, @@ -76,7 +74,7 @@ impl<'a> App<'a> { let focus_trap = Arc::new(AtomicBool::new(false)); - let library = Arc::new(Library::new(theme)); + let library = Rc::new(Library::new(theme)); library.on_enter({ let queue = queue.clone(); @@ -91,21 +89,23 @@ impl<'a> App<'a> { player.play_song(song); } }); - library.on_select_songs_fn({ // selected artist/album + library.on_select_songs_fn({ + // selected artist/album let queue = queue.clone(); let library = library.clone(); move |songs| { log::trace!(target: "::app.library", "on_select_songs_fn -> adding songs to queue"); queue.append(&mut std::collections::VecDeque::from(songs)); - library.on_key(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE)); // hackish way to "select_next()" + // hackish way to "select_next()": + library.on_key(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE)); } }); - let playlist = Arc::new(Playlists::new(theme)); + let playlist = Rc::new(Playlists::new(theme)); playlist.on_enter_song({ let queue = queue.clone(); - move |song | { + move |song| { queue.add_back(song); } }); @@ -133,14 +133,22 @@ impl<'a> App<'a> { let player = player.clone(); let queue = queue.clone(); let playlists = playlist.clone(); - let media_library = Arc::clone(&library); + let media_library = Rc::clone(&library); move |(s, key_event)| { - Self::on_file_browser_select(player.as_ref(), queue.as_ref(), playlists.as_ref(), media_library.as_ref(), s, key_event); + Self::on_file_browser_select( + player.as_ref(), + queue.as_ref(), + playlists.as_ref(), + media_library.as_ref(), + s, + key_event, + ); } }); - let browser = Arc::new(Mutex::new(browser)); + #[allow(clippy::arc_with_non_send_sync)] + let browser = Arc::new(Mutex::new(browser)); // arc_with_non_send_sync gives false positive for browser, but browser will be refactored to use List component anyway let help = Arc::new(Mutex::new(Help::new(theme))); Self { @@ -153,8 +161,8 @@ impl<'a> App<'a> { _music_output: output_stream, screens: vec![ - ("Library".to_string(), Component::RefArc(library.clone())), - ("Playlists".to_string(), Component::RefArc(playlist.clone())), + ("Library".to_string(), Component::RefRc(library.clone())), + ("Playlists".to_string(), Component::RefRc(playlist.clone())), ("Queue".to_string(), Component::RefArc(queue.clone())), ("File Browser".to_string(), Component::Mut(browser.clone())), ("Help".to_string(), Component::Mut(help.clone())), @@ -171,15 +179,17 @@ impl<'a> App<'a> { } } - fn file_browser(&self) -> MutexGuard> { - self.browser.lock().unwrap() - } - fn to_state(&self) -> State { let queue_items = self.queue.songs().clone(); State { - last_visited_path: self.file_browser().current_directory().to_str().map(String::from), + last_visited_path: self + .browser + .lock() + .unwrap() + .current_directory() + .to_str() + .map(String::from), queue_items: Vec::from(queue_items), } } @@ -226,27 +236,30 @@ impl<'a> App<'a> { let player_command_receiver = self.player_command_receiver.clone(); let player = self.player.clone(); - let t = thread::Builder::new().name("media_key_rx".to_string()).spawn(move || { - loop { - match player_command_receiver.lock().unwrap().recv() { - Ok(Command::PlayPause) => { - player.toggle(); - } - Ok(Command::Next) => { - player.stop(); - } - Ok(Command::Quit) => { - log::debug!("Received Command::Quit"); - break; - } - Err(err) => { - log::error!("Channel error: {}", err); - break; + let t = thread::Builder::new() + .name("media_key_rx".to_string()) + .spawn(move || { + loop { + match player_command_receiver.lock().unwrap().recv() { + Ok(Command::PlayPause) => { + player.toggle(); + } + Ok(Command::Next) => { + player.stop(); + } + Ok(Command::Quit) => { + log::debug!("Received Command::Quit"); + break; + } + Err(err) => { + log::error!("Channel error: {}", err); + break; + } } } - } - log::trace!("spawn_media_key_receiver_thread loop exit"); - }).unwrap(); + log::trace!("spawn_media_key_receiver_thread loop exit"); + }) + .unwrap(); self.player_command_receiver_thread = Some(t); } @@ -321,11 +334,11 @@ impl<'a> KeyboardHandlerMut<'a> for App<'a> { } if !self.focus_trap.load(Ordering::Acquire) { match action { - Action::ScreenAction(_) => { + Action::Screen(_) => { self.on_action(action); return; } - Action::PlayerAction(_) => { + Action::Player(_) => { self.player.on_action(action); return; } @@ -349,13 +362,13 @@ impl<'a> WidgetRef for &App<'a> { .style(Style::default().bg(self.theme.background)) .render(area, buf); - let [area_top, _, area_center, area_bottom] = - Layout::vertical([ - Constraint::Length(1), - Constraint::Length(1), - Constraint::Min(0), - Constraint::Length(3), - ]).areas(area); + let [area_top, _, area_center, area_bottom] = Layout::vertical([ + Constraint::Length(1), + Constraint::Length(1), + Constraint::Min(0), + Constraint::Length(3), + ]) + .areas(area); let screen_titles: Vec<&str> = self.screens.iter().map(|screen| screen.0.as_str()).collect(); @@ -373,9 +386,15 @@ impl<'a> WidgetRef for &App<'a> { }; match component { - Component::RefArc(ref s) => { s.render_ref(area_center, buf); } - Component::Mut(ref s) => { s.lock().unwrap().render_ref(area_center, buf); } - _ => {} + Component::RefArc(ref s) => { + s.render_ref(area_center, buf); + } + Component::RefRc(ref s) => { + s.render_ref(area_center, buf); + } + Component::Mut(ref s) => { + s.lock().unwrap().render_ref(area_center, buf); + } } self.player.render(area_bottom, buf); @@ -384,17 +403,14 @@ impl<'a> WidgetRef for &App<'a> { impl<'a> OnActionMut for App<'a> { fn on_action(&mut self, action: Action) { - match action { - Action::ScreenAction(action) => { - match action { - ScreenAction::Library => { self.focused_screen = 0 } - ScreenAction::Playlists => { self.focused_screen = 1 } - ScreenAction::Queue => { self.focused_screen = 2 } - ScreenAction::FileBrowser => { self.focused_screen = 3 } - ScreenAction::Help => { self.focused_screen = 4 } - } + if let Action::Screen(action) = action { + match action { + ScreenAction::Library => self.focused_screen = 0, + ScreenAction::Playlists => self.focused_screen = 1, + ScreenAction::Queue => self.focused_screen = 2, + ScreenAction::FileBrowser => self.focused_screen = 3, + ScreenAction::Help => self.focused_screen = 4, } - _ => {} } } } diff --git a/src/auto_update.rs b/src/auto_update.rs index c50ab20..bffbb83 100644 --- a/src/auto_update.rs +++ b/src/auto_update.rs @@ -40,8 +40,8 @@ pub enum ReleasesError { impl std::fmt::Debug for ReleasesError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ReleasesError::Reqwest(e) => { e.fmt(f) } - ReleasesError::TomlFileError(e) => { e.fmt(f) } + ReleasesError::Reqwest(e) => e.fmt(f), + ReleasesError::TomlFileError(e) => e.fmt(f), } } } @@ -73,9 +73,7 @@ pub fn get_releases() -> Result<(), ReleasesError> { log::debug!(target: "::get_releases", "Got releases = {releases:#?}"); - let releases = Releases { - releases, - }; + let releases = Releases { releases }; write_toml_file("releases", &releases)?; @@ -91,8 +89,7 @@ pub fn can_i_has_rls() -> Result<(), ReleasesError> { log::trace!(target: target, "we has rls file = {releases:#?}"); - log::trace!(target: target, "we has rls published_at = {:?}", releases.releases.get(0).map(|r| r.published_at.as_str())); - + log::trace!(target: target, "we has rls published_at = {:?}", releases.releases.first().map(|r| r.published_at.as_str())); Ok(()) } diff --git a/src/bye.rs b/src/bye.rs index 8f50b88..bbcbf26 100644 --- a/src/bye.rs +++ b/src/bye.rs @@ -1,4 +1,4 @@ -use std::time::{UNIX_EPOCH, Duration, SystemTime}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; const BYE_N: usize = 9; const BYE: [&str; BYE_N] = [ @@ -15,7 +15,9 @@ const BYE: [&str; BYE_N] = [ /// The important things in life pub fn bye() -> &'static str { - let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or(Duration::from_secs(0)); + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or(Duration::from_secs(0)); let now = now.as_micros() as usize; let i = now % BYE_N; BYE[i] diff --git a/src/components.rs b/src/components.rs index e22a533..ff319ba 100644 --- a/src/components.rs +++ b/src/components.rs @@ -1,14 +1,14 @@ mod file_browser; -mod library; -mod queue; mod help; -mod playlists; +mod library; mod list; +mod playlists; +mod queue; mod rendering_error; pub use file_browser::{directory_to_songs_and_folders, FileBrowser, FileBrowserSelection}; +pub use help::Help; pub use library::Library; +pub use list::List; pub use playlists::Playlists; pub use queue::Queue; -pub use help::Help; -pub use list::List; diff --git a/src/components/file_browser.rs b/src/components/file_browser.rs index 61107a5..bd1443e 100644 --- a/src/components/file_browser.rs +++ b/src/components/file_browser.rs @@ -1,7 +1,7 @@ pub mod file_browser; -pub mod widget; -pub mod keyboard_handler; mod file_browser_selection; +pub mod keyboard_handler; +pub mod widget; pub use file_browser::*; -pub use file_browser_selection::{FileBrowserSelection, directory_to_songs_and_folders}; +pub use file_browser_selection::{directory_to_songs_and_folders, FileBrowserSelection}; diff --git a/src/components/file_browser/file_browser.rs b/src/components/file_browser/file_browser.rs index 5201f88..da79770 100644 --- a/src/components/file_browser/file_browser.rs +++ b/src/components/file_browser/file_browser.rs @@ -1,5 +1,5 @@ use std::{ - path::PathBuf, + path::{Path, PathBuf}, sync::Mutex, }; @@ -7,10 +7,7 @@ use crossterm::event::{KeyCode, KeyEvent}; use crate::config::Theme; -use super::file_browser_selection::{ - FileBrowserSelection, - directory_to_songs_and_folders, -}; +use super::file_browser_selection::{directory_to_songs_and_folders, FileBrowserSelection}; pub struct FileBrowser<'a> { on_select_fn: Box, @@ -100,7 +97,9 @@ impl<'a> FileBrowser<'a> { } pub fn navigate_up(&mut self) { - let Some(parent) = self.current_directory.as_path().parent().map(|p| p.to_path_buf()) else { return }; + let Some(parent) = self.current_directory.as_path().parent().map(|p| p.to_path_buf()) else { + return; + }; self.items = directory_to_songs_and_folders(&parent); self.select_current_directory(); self.current_directory = parent; @@ -152,7 +151,7 @@ impl<'a> FileBrowser<'a> { } } - pub fn find_by_path(&self, s: &PathBuf) -> usize { + pub fn find_by_path(&self, s: &Path) -> usize { let mut i = 0; for n in 0..self.items.len() { @@ -183,7 +182,12 @@ impl<'a> FileBrowser<'a> { return None; } - if self.items[i].to_path().to_string_lossy().to_lowercase().contains(&s.to_lowercase()) { + if self.items[i] + .to_path() + .to_string_lossy() + .to_lowercase() + .contains(&s.to_lowercase()) + { return Some(i); } } @@ -231,13 +235,11 @@ impl<'a> FileBrowser<'a> { pub fn filter_delete(&mut self) { self.filter = match &self.filter { - Some(s) if s.len() > 0 => Some(s[..s.len() - 1].to_string()), // TODO: s[..s.len()-1] can panic! use .substring crate + Some(s) if !s.is_empty() => Some(s[..s.len() - 1].to_string()), // TODO: s[..s.len()-1] can panic! use .substring crate _ => None, }; } - - fn padding_top(&self) -> usize { 6 } @@ -248,12 +250,12 @@ impl<'a> FileBrowser<'a> { pub fn set_offset(&mut self, i: usize, padding: usize) { self.offset = if i > padding { - i.saturating_sub(padding).min(self.items.len().saturating_sub(*self.height.lock().unwrap())) + i.saturating_sub(padding) + .min(self.items.len().saturating_sub(*self.height.lock().unwrap())) } else { 0 }; } - } impl Drop for FileBrowser<'_> { diff --git a/src/components/file_browser/file_browser_selection.rs b/src/components/file_browser/file_browser_selection.rs index 5b5fa98..5d08fbb 100644 --- a/src/components/file_browser/file_browser_selection.rs +++ b/src/components/file_browser/file_browser_selection.rs @@ -1,18 +1,18 @@ use std::{ + cmp::Ordering, fs, fs::DirEntry, - path::PathBuf, - cmp::Ordering, + path::{Path, PathBuf}, }; use crate::{ - structs::{Song, Jolt}, cue::CueSheet, + structs::{Jolt, Song}, }; const VALID_EXTENSIONS: [&str; 7] = ["mp3", "mp4", "m4a", "wav", "flac", "ogg", "aac"]; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq)] pub enum FileBrowserSelection { Song(Song), CueSheet(CueSheet), @@ -25,18 +25,18 @@ impl FileBrowserSelection { if path.is_dir() { Some(FileBrowserSelection::Directory(path.clone())) } else if path.extension().is_some_and(|e| e == "cue") { - CueSheet::from_file(&path).ok().map(FileBrowserSelection::CueSheet) + CueSheet::from_file(path).ok().map(FileBrowserSelection::CueSheet) } else { - Song::from_file(&path).ok().map(FileBrowserSelection::Song) + Song::from_file(path).ok().map(FileBrowserSelection::Song) } } pub fn to_path(&self) -> PathBuf { match self { - FileBrowserSelection::Song(s) => { s.path.clone() } - FileBrowserSelection::CueSheet(cs) => { cs.cue_sheet_file_path() } - FileBrowserSelection::Directory(p) => { p.clone() } - FileBrowserSelection::Jolt(j) => { j.path.clone() } + FileBrowserSelection::Song(s) => s.path.clone(), + FileBrowserSelection::CueSheet(cs) => cs.cue_sheet_file_path(), + FileBrowserSelection::Directory(p) => p.clone(), + FileBrowserSelection::Jolt(j) => j.path.clone(), } } } @@ -44,40 +44,37 @@ impl FileBrowserSelection { impl PartialEq for FileBrowserSelection { fn eq(&self, other: &Self) -> bool { match self { - FileBrowserSelection::Directory(path) => { - match other { - FileBrowserSelection::Directory(other_path) => path == other_path, - _ => false, - } - } - FileBrowserSelection::CueSheet(cue_sheet) => { - match other { - FileBrowserSelection::CueSheet(other_cue_sheet) => - cue_sheet.cue_sheet_file_path() == other_cue_sheet.cue_sheet_file_path(), - _ => false, - } - } - FileBrowserSelection::Song(song) => { - match other { - FileBrowserSelection::Song(other_song) => song.path == other_song.path, - _ => false, - } - } - FileBrowserSelection::Jolt(jolt) => { - match other { - FileBrowserSelection::Jolt(j) => jolt.path == j.path, - _ => false, + FileBrowserSelection::Directory(path) => match other { + FileBrowserSelection::Directory(other_path) => path == other_path, + _ => false, + }, + FileBrowserSelection::CueSheet(cue_sheet) => match other { + FileBrowserSelection::CueSheet(other_cue_sheet) => { + cue_sheet.cue_sheet_file_path() == other_cue_sheet.cue_sheet_file_path() } - } + _ => false, + }, + FileBrowserSelection::Song(song) => match other { + FileBrowserSelection::Song(other_song) => song.path == other_song.path, + _ => false, + }, + FileBrowserSelection::Jolt(jolt) => match other { + FileBrowserSelection::Jolt(j) => jolt.path == j.path, + _ => false, + }, } } } -impl Eq for FileBrowserSelection {} - impl PartialOrd for FileBrowserSelection { fn partial_cmp(&self, other: &Self) -> Option { - Some(match self { + Some(self.cmp(other)) + } +} + +impl Ord for FileBrowserSelection { + fn cmp(&self, other: &Self) -> Ordering { + match self { FileBrowserSelection::Directory(path) => { // Directories come first match other { @@ -98,8 +95,9 @@ impl PartialOrd for FileBrowserSelection { match other { FileBrowserSelection::Directory(_) => Ordering::Greater, FileBrowserSelection::Jolt(_) => Ordering::Greater, - FileBrowserSelection::CueSheet(other_cue_sheet) => - cue_sheet.cue_sheet_file_path().cmp(&other_cue_sheet.cue_sheet_file_path()), + FileBrowserSelection::CueSheet(other_cue_sheet) => cue_sheet + .cue_sheet_file_path() + .cmp(&other_cue_sheet.cue_sheet_file_path()), _ => Ordering::Less, } } @@ -110,30 +108,26 @@ impl PartialOrd for FileBrowserSelection { _ => Ordering::Greater, } } - }) - } -} - -impl Ord for FileBrowserSelection { - fn cmp(&self, other: &Self) -> Ordering { - self.partial_cmp(other).unwrap() + } } } -fn dir_entry_to_file_browser_selection(entry: &DirEntry) -> Option{ - if dir_entry_is_dir(&entry) { +fn dir_entry_to_file_browser_selection(entry: &DirEntry) -> Option { + if dir_entry_is_dir(entry) { Some(FileBrowserSelection::Directory(entry.path())) - } else if dir_entry_is_song(&entry) { + } else if dir_entry_is_song(entry) { match Song::from_file(&entry.path()).map(FileBrowserSelection::Song) { Ok(a) => Some(a), Err(err) => { log::warn!("dir_entry_to_file_browser_selection {:#?} {:#?}", &entry.path(), err); None - }, + } } - } else if dir_entry_is_cue(&entry) { - Some(FileBrowserSelection::CueSheet(CueSheet::from_file(&entry.path()).unwrap())) - } else if dir_entry_is_jolt_file(&entry) { + } else if dir_entry_is_cue(entry) { + Some(FileBrowserSelection::CueSheet( + CueSheet::from_file(&entry.path()).unwrap(), + )) + } else if dir_entry_is_jolt_file(entry) { match Jolt::from_path(entry.path()) { Ok(jolt) => Some(FileBrowserSelection::Jolt(jolt)), Err(err) => { @@ -146,7 +140,7 @@ fn dir_entry_to_file_browser_selection(entry: &DirEntry) -> Option Vec { +pub fn directory_to_songs_and_folders(path: &Path) -> Vec { let Ok(entries) = path.read_dir() else { return vec![]; }; @@ -168,7 +162,10 @@ pub fn dir_entry_is_file(dir_entry: &DirEntry) -> bool { pub fn dir_entry_is_dir(dir_entry: &DirEntry) -> bool { let Ok(ft) = dir_entry.file_type() else { - log::error!("dir_entry_is_dir: .file_type() returned error for {:?}", dir_entry.path()); + log::error!( + "dir_entry_is_dir: .file_type() returned error for {:?}", + dir_entry.path() + ); return false; }; @@ -181,7 +178,7 @@ pub fn dir_entry_is_dir(dir_entry: &DirEntry) -> bool { } #[allow(dead_code)] -pub fn path_is_not_hidden(path: &PathBuf) -> bool { +pub fn path_is_not_hidden(path: &Path) -> bool { path.file_name() .and_then(|e| e.to_str()) .map(|e| e.to_string()) @@ -200,17 +197,11 @@ pub fn dir_entry_is_song(dir_entry: &DirEntry) -> bool { } pub fn dir_entry_has_cue_extension(dir_entry: &DirEntry) -> bool { - dir_entry - .path() - .extension() - .is_some_and(|e| e == "cue") + dir_entry.path().extension().is_some_and(|e| e == "cue") } pub fn dir_entry_is_jolt_file(dir_entry: &DirEntry) -> bool { - dir_entry - .path() - .file_name() - .is_some_and(|e| e == ".jolt") + dir_entry.path().file_name().is_some_and(|e| e == ".jolt") } pub fn dir_entry_is_cue(dir_entry: &DirEntry) -> bool { diff --git a/src/components/file_browser/keyboard_handler.rs b/src/components/file_browser/keyboard_handler.rs index da49c77..01bc7fc 100644 --- a/src/components/file_browser/keyboard_handler.rs +++ b/src/components/file_browser/keyboard_handler.rs @@ -6,7 +6,7 @@ use super::FileBrowser; impl<'a> KeyboardHandlerMut<'a> for FileBrowser<'a> { fn on_key(&mut self, key: KeyEvent) { - if !self.filter.is_some() { + if self.filter.is_none() { self.on_normal_key_event(key); } else { self.on_filter_key_event(key); @@ -20,10 +20,10 @@ impl<'a> FileBrowser<'a> { KeyCode::Backspace => self.navigate_up(), KeyCode::Down => { self.select_next(); - }, + } KeyCode::Up => { self.select_previous(); - }, + } // KeyCode::PageUp => self.items.previous_by(5), // KeyCode::PageDown => self.items.next_by(5), KeyCode::End => self.select_last(), @@ -33,7 +33,7 @@ impl<'a> FileBrowser<'a> { } KeyCode::Enter | KeyCode::Char(_) => { self.enter_selection(key); - }, + } _ => {} } } diff --git a/src/components/file_browser/widget.rs b/src/components/file_browser/widget.rs index 4c50ff9..8ac1e14 100644 --- a/src/components/file_browser/widget.rs +++ b/src/components/file_browser/widget.rs @@ -6,16 +6,7 @@ use ratatui::{ layout::{Alignment, Constraint, Layout, Rect}, prelude::{Line, Modifier, Span, Style}, text::Text, - widgets::{ - block::Position, - Block, - Borders, - List, - WidgetRef, - StatefulWidget, - ListState, - ListItem, - }, + widgets::{block::Position, Block, Borders, List, ListItem, ListState, StatefulWidget, WidgetRef}, }; use crate::config::Theme; @@ -51,11 +42,14 @@ impl<'a> WidgetRef for FileBrowser<'a> { fl, area_main_left, buf, - &mut ListState::default().with_offset(self.offset).with_selected(Some(self.selected_index)), + &mut ListState::default() + .with_offset(self.offset) + .with_selected(Some(self.selected_index)), ); - let [_separator_left, _, _separator_right] = Layout::horizontal([Constraint::Min(1), Constraint::Length(1), Constraint::Min(1)]) - .areas(area_main_separator); + let [_separator_left, _, _separator_right] = + Layout::horizontal([Constraint::Min(1), Constraint::Length(1), Constraint::Min(1)]) + .areas(area_main_separator); } } @@ -64,13 +58,12 @@ fn create_areas(area: Rect) -> (Rect, Rect, Rect, Rect) { .horizontal_margin(2) .areas(area); - let [area_main_left, area_main_separator, area_main_right] = - Layout::horizontal([ - Constraint::Percentage(50), - Constraint::Length(5), - Constraint::Percentage(50), - ]) - .areas(area_main); + let [area_main_left, area_main_separator, area_main_right] = Layout::horizontal([ + Constraint::Percentage(50), + Constraint::Length(5), + Constraint::Percentage(50), + ]) + .areas(area_main); (area_top, area_main_left, area_main_separator, area_main_right) } @@ -78,8 +71,7 @@ fn create_areas(area: Rect) -> (Rect, Rect, Rect, Rect) { fn top_bar(theme: &Theme, current_directory: &Path, filter: &Option) -> Block<'static> { let folder_name = current_directory .file_name() - .map(|s| s.to_str()) - .flatten() + .and_then(|s| s.to_str()) .map(String::from) .unwrap_or("".to_string()); @@ -101,7 +93,7 @@ fn top_bar(theme: &Theme, current_directory: &Path, filter: &Option) -> top_bar } -fn file_list(theme: &Theme, items: &Vec, filter: &Option) -> List<'static> { +fn file_list(theme: &Theme, items: &[FileBrowserSelection], filter: &Option) -> List<'static> { let browser_items: Vec = items .iter() // .map(|i| i.to_path().to_string_lossy().to_string()) diff --git a/src/components/help/help.rs b/src/components/help/help.rs index 83da411..d96bc0e 100644 --- a/src/components/help/help.rs +++ b/src/components/help/help.rs @@ -1,15 +1,12 @@ use crossterm::event::{KeyCode, KeyEvent}; use ratatui::{ + buffer::Buffer, layout::{Alignment, Constraint, Layout, Rect}, style::{Modifier, Style}, widgets::{Block, BorderType, Borders, Cell, Row, Table, TableState, WidgetRef}, - buffer::Buffer, }; -use crate::{ - config::Theme, - ui::{KeyboardHandlerMut}, -}; +use crate::{config::Theme, ui::KeyboardHandlerMut}; pub struct Help<'a> { theme: Theme, @@ -76,7 +73,7 @@ impl<'a> KeyboardHandlerMut<'a> for Help<'a> { match key.code { KeyCode::Down | KeyCode::Char('j') => self.next(), KeyCode::Up | KeyCode::Char('k') => self.previous(), - _ => {}, + _ => {} } } } @@ -100,11 +97,7 @@ impl<'a> WidgetRef for Help<'a> { .map(|h| Cell::from(*h).style(Style::default().fg(self.theme.foreground_selected))); let header = Row::new(header) - .style( - Style::default() - .bg(self.theme.background) - .fg(self.theme.foreground), - ) + .style(Style::default().bg(self.theme.background).fg(self.theme.foreground)) .height(1) .bottom_margin(0); @@ -130,27 +123,20 @@ impl<'a> WidgetRef for Help<'a> { .title_alignment(Alignment::Center) .border_type(BorderType::Plain), ) - .style( - Style::default() - .fg(self.theme.foreground) - .bg(self.theme.background), - ) + .style(Style::default().fg(self.theme.foreground).bg(self.theme.background)) .row_highlight_style( Style::default() .add_modifier(Modifier::BOLD) .bg(self.theme.background_selected) .fg(self.theme.foreground_selected), ) - .widths(&[Constraint::Percentage(50), Constraint::Length(30), Constraint::Min(10)]); + .widths([Constraint::Percentage(50), Constraint::Length(30), Constraint::Min(10)]); ratatui::widgets::StatefulWidgetRef::render_ref(&table, area_main, buf, &mut self.state.clone()); // table.render_ref(layout[0], buf, &mut self.state.clone()); - } } - - impl Drop for Help<'_> { fn drop(&mut self) { log::trace!("HelpTab.drop()"); diff --git a/src/components/library.rs b/src/components/library.rs index 506d24f..8322be7 100644 --- a/src/components/library.rs +++ b/src/components/library.rs @@ -1,6 +1,6 @@ +pub mod keyboard_handler; pub mod library; pub mod widget; -pub mod keyboard_handler; mod album_tree; diff --git a/src/components/library/album_tree.rs b/src/components/library/album_tree.rs index 52644c5..bd9be26 100644 --- a/src/components/library/album_tree.rs +++ b/src/components/library/album_tree.rs @@ -1,7 +1,7 @@ +mod album_tree_item; mod component; -mod widget; mod keyboard_handler; -mod album_tree_item; +mod widget; -pub use component::AlbumTree; pub use album_tree_item::AlbumTreeItem; +pub use component::AlbumTree; diff --git a/src/components/library/album_tree/album_tree_item.rs b/src/components/library/album_tree/album_tree_item.rs index ad1ce0a..a552b4a 100644 --- a/src/components/library/album_tree/album_tree_item.rs +++ b/src/components/library/album_tree/album_tree_item.rs @@ -12,18 +12,10 @@ pub enum AlbumTreeItem { impl Ord for AlbumTreeItem { fn cmp(&self, other: &Self) -> Ordering { match (self, other) { - (AlbumTreeItem::Artist(a), AlbumTreeItem::Artist(b)) => { - a.cmp(b) - } - (AlbumTreeItem::Artist(_), AlbumTreeItem::Album(_, _)) => { - Ordering::Greater - } - (AlbumTreeItem::Album(_, _), AlbumTreeItem::Artist(_)) => { - Ordering::Less - } - (AlbumTreeItem::Album(_, ref a), AlbumTreeItem::Album(_, ref b)) => { - a.cmp(b) - } + (AlbumTreeItem::Artist(a), AlbumTreeItem::Artist(b)) => a.cmp(b), + (AlbumTreeItem::Artist(_), AlbumTreeItem::Album(_, _)) => Ordering::Greater, + (AlbumTreeItem::Album(_, _), AlbumTreeItem::Artist(_)) => Ordering::Less, + (AlbumTreeItem::Album(_, ref a), AlbumTreeItem::Album(_, ref b)) => a.cmp(b), } } } diff --git a/src/components/library/album_tree/component.rs b/src/components/library/album_tree/component.rs index fee1ea8..7e247f9 100644 --- a/src/components/library/album_tree/component.rs +++ b/src/components/library/album_tree/component.rs @@ -1,13 +1,9 @@ -use std::{ - sync::{ - atomic::{AtomicUsize, Ordering as AtomicOrdering}, - Mutex, - }, +use std::sync::{ + atomic::{AtomicUsize, Ordering as AtomicOrdering}, + Mutex, }; -use crate::{ - config::Theme, -}; +use crate::config::Theme; use super::AlbumTreeItem; @@ -71,9 +67,7 @@ impl<'a> AlbumTree<'a> { pub fn selected_item(&self) -> Option { let i = self.selected_artist.load(AtomicOrdering::SeqCst); let artist_list = self.artist_list.lock().unwrap(); - let Some(artist) = artist_list.get(i) else { - return None; - }; + let artist = artist_list.get(i)?; if !artist.is_open { Some(AlbumTreeItem::Artist(artist.artist.clone())) @@ -81,12 +75,15 @@ impl<'a> AlbumTree<'a> { let selected_album = self.selected_album.load(AtomicOrdering::SeqCst); let Some(album) = artist.albums.get(selected_album) else { - log::error!("artist {} selected_album {selected_album} >= len {}", artist.artist, artist.albums.len()); + log::error!( + "artist {} selected_album {selected_album} >= len {}", + artist.artist, + artist.albums.len() + ); panic!("no album at selected index!"); }; Some(AlbumTreeItem::Album(artist.artist.clone(), album.clone())) - } } @@ -100,12 +97,15 @@ impl<'a> AlbumTree<'a> { } } Err(i) => { - artists.insert(i, AlbumTreeEntryArtist { - artist: artist.clone(), - albums: vec![], - is_open: false, - is_match: false, - }); + artists.insert( + i, + AlbumTreeEntryArtist { + artist: artist.clone(), + albums: vec![], + is_open: false, + is_match: false, + }, + ); } } } diff --git a/src/components/library/album_tree/keyboard_handler.rs b/src/components/library/album_tree/keyboard_handler.rs index 57f4cba..450fe9f 100644 --- a/src/components/library/album_tree/keyboard_handler.rs +++ b/src/components/library/album_tree/keyboard_handler.rs @@ -2,17 +2,11 @@ use std::sync::atomic::Ordering; use crossterm::event::{KeyCode, KeyEvent}; -use crate::{ - ui::KeyboardHandlerRef, -}; +use crate::ui::KeyboardHandlerRef; -use super::{ - component::AlbumTree, - album_tree_item::AlbumTreeItem, -}; +use super::{album_tree_item::AlbumTreeItem, component::AlbumTree}; impl<'a> KeyboardHandlerRef<'a> for AlbumTree<'a> { - fn on_key(&self, key: KeyEvent) { let target = "::ArtistList.on_key"; log::trace!(target: target, "{:?}", key); @@ -26,14 +20,14 @@ impl<'a> KeyboardHandlerRef<'a> for AlbumTree<'a> { return; }; self.on_select_fn.lock().unwrap()(item); - }, + } KeyCode::Enter => { let Some(item) = self.selected_item() else { log::warn!(target: target, "No selected item"); return; }; self.on_confirm_fn.lock().unwrap()(item); - }, + } KeyCode::Char(' ') => { let selected_artist = self.selected_artist.load(Ordering::SeqCst); let mut artist_list = self.artist_list.lock().unwrap(); @@ -46,11 +40,11 @@ impl<'a> KeyboardHandlerRef<'a> for AlbumTree<'a> { let item = AlbumTreeItem::Artist(selected_artist.artist.clone()); self.on_select_fn.lock().unwrap()(item); } else { - let album = selected_artist.albums.get(0).unwrap(); + let album = selected_artist.albums.first().unwrap(); let item = AlbumTreeItem::Album(selected_artist.artist.clone(), album.clone()); self.on_select_fn.lock().unwrap()(item); } - }, + } KeyCode::Delete => { let selected_artist = self.selected_artist.load(Ordering::SeqCst); let selected_album = self.selected_album.load(Ordering::SeqCst); @@ -70,10 +64,8 @@ impl<'a> KeyboardHandlerRef<'a> for AlbumTree<'a> { removed_item = AlbumTreeItem::Album(artist.artist.clone(), removed_album); newly_selected_album_index = selected_album.min(artist.albums.len().saturating_sub(1)); - newly_selected_item = AlbumTreeItem::Album( - artist.artist.clone(), - artist.albums[newly_selected_album_index].clone(), - ); + newly_selected_item = + AlbumTreeItem::Album(artist.artist.clone(), artist.albums[newly_selected_album_index].clone()); } else { let removed_artist = artists.remove(selected_artist); removed_item = AlbumTreeItem::Artist(removed_artist.artist); @@ -81,7 +73,8 @@ impl<'a> KeyboardHandlerRef<'a> for AlbumTree<'a> { let newly_selected_artist_index = selected_artist.saturating_sub(1); let newly_selected_artist = artists.get(newly_selected_artist_index).unwrap(); - self.selected_artist.store(newly_selected_artist_index, Ordering::SeqCst); + self.selected_artist + .store(newly_selected_artist_index, Ordering::SeqCst); if newly_selected_artist.is_open { newly_selected_album_index = newly_selected_artist.albums.len().saturating_sub(1); @@ -103,7 +96,7 @@ impl<'a> KeyboardHandlerRef<'a> for AlbumTree<'a> { self.on_delete_fn.lock().unwrap()(removed_item); self.on_select_fn.lock().unwrap()(newly_selected_item); - }, + } KeyCode::Char(char) => { let item = { let mut filter = self.filter.lock().unwrap(); @@ -114,18 +107,18 @@ impl<'a> KeyboardHandlerRef<'a> for AlbumTree<'a> { for i in 0..artists.len() { let entry = &mut artists[i]; - entry.is_match = entry.artist.contains(filter.as_str()) || entry.artist.to_lowercase().contains(filter.to_lowercase().as_str()); + entry.is_match = entry.artist.contains(filter.as_str()) + || entry.artist.to_lowercase().contains(filter.to_lowercase().as_str()); } let selected_artist_index = self.selected_artist.load(Ordering::SeqCst); let selected_artist = &artists[selected_artist_index]; if !selected_artist.is_match { - if let Some(n) = artists.iter().position(|entry| entry.is_match) { - Some((AlbumTreeItem::Artist(artists[n].artist.clone()), n)) - } else { - None - } + artists + .iter() + .position(|entry| entry.is_match) + .map(|n| (AlbumTreeItem::Artist(artists[n].artist.clone()), n)) } else { None } @@ -146,14 +139,12 @@ impl<'a> KeyboardHandlerRef<'a> for AlbumTree<'a> { entry.is_match = false; } } - _ => {}, + _ => {} } } } - impl<'a> AlbumTree<'a> { - fn on_artist_list_directional_key(&self, key: KeyEvent) { let artists = self.artist_list.lock().unwrap(); let length = { @@ -184,18 +175,16 @@ impl<'a> AlbumTree<'a> { 0 }; } - } else { - if artist.is_open { - if j < artist.albums.len().saturating_sub(1) as i32 { - j += 1; - } else if i < artists.len().saturating_sub(1) as i32 { - j = 0; - i += 1; - } - } else { + } else if artist.is_open { + if j < artist.albums.len().saturating_sub(1) as i32 { + j += 1; + } else if i < artists.len().saturating_sub(1) as i32 { j = 0; i += 1; } + } else { + j = 0; + i += 1; } let padding = if key.code == KeyCode::Up { @@ -204,23 +193,29 @@ impl<'a> AlbumTree<'a> { height.saturating_sub(padding).saturating_sub(1) }; - let visible_items: usize = artists.iter().take(i as usize).filter(|a| a.is_open).map(|a| a.albums.len()).sum(); + let visible_items: usize = artists + .iter() + .take(i as usize) + .filter(|a| a.is_open) + .map(|a| a.albums.len()) + .sum(); let visible_items = visible_items as i32 + i + j; - if (key.code == KeyCode::Up && visible_items < offset + padding + 1) || (key.code == KeyCode::Down && visible_items > offset + padding) { + if (key.code == KeyCode::Up && visible_items < offset + padding + 1) + || (key.code == KeyCode::Down && visible_items > offset + padding) + { offset = if visible_items > padding { visible_items - padding } else { 0 }; } - - }, + } KeyCode::Home => { i = 0; j = 0; offset = 0; - }, + } KeyCode::End => { i = artists.len() as i32 - 1; let artist = artists.get(i.max(0) as usize).unwrap(); @@ -230,8 +225,8 @@ impl<'a> AlbumTree<'a> { 0 } as i32; offset = length - 1 - height + padding; - }, - _ => {}, + } + _ => {} } offset = offset.min(length - height).max(0); @@ -241,5 +236,4 @@ impl<'a> AlbumTree<'a> { self.selected_artist.store(i as usize, Ordering::SeqCst); self.selected_album.store(j as usize, Ordering::SeqCst); } - } diff --git a/src/components/library/album_tree/widget.rs b/src/components/library/album_tree/widget.rs index 6d8da2d..3acc452 100644 --- a/src/components/library/album_tree/widget.rs +++ b/src/components/library/album_tree/widget.rs @@ -1,23 +1,21 @@ use std::sync::atomic::Ordering; -use ratatui::{ - buffer::Buffer, - layout::Rect, - style::Style, - widgets::WidgetRef, - text::Line, -}; +use ratatui::{buffer::Buffer, layout::Rect, style::Style, text::Line, widgets::WidgetRef}; use crate::config::Theme; -use super::component::{AlbumTree}; +use super::component::AlbumTree; fn line_style(theme: &Theme, list_has_focus: bool, is_selected: bool, is_search_match: bool) -> Style { if is_selected { if list_has_focus { - Style::default().fg(theme.foreground_selected).bg(theme.background_selected) + Style::default() + .fg(theme.foreground_selected) + .bg(theme.background_selected) } else { - Style::default().fg(theme.foreground_selected).bg(theme.background_selected_blur) + Style::default() + .fg(theme.foreground_selected) + .bg(theme.background_selected_blur) } } else { let c = if is_search_match { @@ -63,16 +61,13 @@ impl<'a> WidgetRef for AlbumTree<'a> { return; } + #[allow(clippy::needless_range_loop)] for i in offset..list.len().min(offset + area.height as usize) { let (text, is_album, is_selected, is_match) = list[i]; let style = line_style(&self.theme, true, is_selected, is_match); let rect = Rect { - x: if is_album{ - area.x + 2 - } else { - area.x - }, + x: if is_album { area.x + 2 } else { area.x }, y: area.y + i as u16 - offset as u16, width: area.width, height: 1, diff --git a/src/components/library/keyboard_handler.rs b/src/components/library/keyboard_handler.rs index 8bcad87..65faa51 100644 --- a/src/components/library/keyboard_handler.rs +++ b/src/components/library/keyboard_handler.rs @@ -1,12 +1,9 @@ use crossterm::event::{KeyCode, KeyEvent}; -use crate::{ - ui::{KeyboardHandlerRef}, -}; -use super::library::{Library}; +use super::library::Library; +use crate::ui::KeyboardHandlerRef; impl<'a> KeyboardHandlerRef<'a> for Library<'a> { - fn on_key(&self, key: KeyEvent) { log::trace!(target: "::library.on_key", "{:?}", key); diff --git a/src/components/library/library.rs b/src/components/library/library.rs index df345dd..cc65614 100644 --- a/src/components/library/library.rs +++ b/src/components/library/library.rs @@ -1,25 +1,17 @@ use std::{ cell::Cell, + path::Path, rc::Rc, - sync::{ - Mutex, - MutexGuard, - }, - path::PathBuf, + sync::{Mutex, MutexGuard}, }; use crossterm::event::KeyEvent; use crate::{ - structs::{Song}, - config::Theme, - cue::CueSheet, - components::List, - components::list::Direction, - ui::Component, + components::list::Direction, components::List, config::Theme, cue::CueSheet, structs::Song, ui::Component, }; -use super::{album_tree::{AlbumTree, AlbumTreeItem}}; +use super::album_tree::{AlbumTree, AlbumTreeItem}; pub struct Library<'a> { #[allow(dead_code)] @@ -32,13 +24,13 @@ pub struct Library<'a> { pub(super) components: Rc>>, pub(super) focused_component: Rc>, - pub(super) on_select_fn: Rc>>, pub(super) on_select_songs_fn: Rc) + 'a>>>, } impl<'a> Library<'a> { pub fn new(theme: Theme) -> Self { - let on_select_fn: Rc>> = Rc::new(Mutex::new(Box::new(|_, _| {}) as _)); + let on_select_fn: Rc>> = + Rc::new(Mutex::new(Box::new(|_, _| {}) as _)); let on_select_songs_fn: Rc) + 'a>>> = Rc::new(Mutex::new(Box::new(|_| {}) as _)); let songs_by_artist = Rc::new(Mutex::new(crate::files::Library::from_file())); @@ -54,21 +46,21 @@ impl<'a> Library<'a> { let (artist, album) = match item { AlbumTreeItem::Artist(artist) => (artist, None), - AlbumTreeItem::Album(artist, album) => (artist, Some(album)) + AlbumTreeItem::Album(artist, album) => (artist, Some(album)), }; let artist_songs = { let songs = songs.lock().unwrap(); match songs.songs_by_artist.get(artist.as_str()) { - Some(artist_songs) => { - match album { - Some(album) => { - artist_songs.iter().filter(|s| s.album.as_ref().is_some_and(|a| *a == album)).cloned().collect() - } - None => artist_songs.clone(), - } - } + Some(artist_songs) => match album { + Some(album) => artist_songs + .iter() + .filter(|s| s.album.as_ref().is_some_and(|a| *a == album)) + .cloned() + .collect(), + None => artist_songs.clone(), + }, None => { log::error!(target: "::library.album_tree.on_select", "artist with no songs {artist}"); vec![] @@ -88,12 +80,8 @@ impl<'a> Library<'a> { log::trace!(target: "::library.album_tree.on_confirm", "artist confirmed {:?}", item); let (artist, album) = match item { - AlbumTreeItem::Artist(artist) => { - (artist, None) - } - AlbumTreeItem::Album(artist, album) => { - (artist, Some(album)) - } + AlbumTreeItem::Artist(artist) => (artist, None), + AlbumTreeItem::Album(artist, album) => (artist, Some(album)), }; let songs = { @@ -104,9 +92,13 @@ impl<'a> Library<'a> { }; if let Some(album) = album { - songs.iter().filter(|s| s.album.as_ref().is_some_and(|a| *a == album)).cloned().collect() + songs + .iter() + .filter(|s| s.album.as_ref().is_some_and(|a| *a == album)) + .cloned() + .collect() } else { - songs.iter().cloned().collect() + songs.to_vec() } }; @@ -163,13 +155,11 @@ impl<'a> Library<'a> { .take(i) .rposition(|s| s.album.as_ref().is_some_and(|a| a != selected_album)) .and_then(|ns| songs.get(ns)) - .and_then(|ref s| s.album.as_ref()) + .and_then(|s| s.album.as_ref()) .and_then(|next_song_album| { songs .iter() - .position(|song| { - song.album.as_ref().is_some_and(|a| a.as_str() == next_song_album) - }) + .position(|song| song.album.as_ref().is_some_and(|a| a.as_str() == next_song_album)) }) } } @@ -184,7 +174,6 @@ impl<'a> Library<'a> { theme, focused_component: Rc::new(Cell::new(0)), - on_select_fn, on_select_songs_fn, songs_by_artist, @@ -197,10 +186,6 @@ impl<'a> Library<'a> { lib } - pub fn on_select(&self, cb: impl FnMut(Song, KeyEvent) + 'a) { - *self.on_select_fn.lock().unwrap() = Box::new(cb); - } - pub fn on_enter(&self, cb: impl Fn(Song) + 'a) { self.song_list.on_enter(cb); } @@ -222,9 +207,9 @@ impl<'a> Library<'a> { } pub fn refresh_components(&self) { - let mut songs_by_artist = self.songs_by_artist.lock().unwrap(); - self.refresh_artist_tree(&mut songs_by_artist); - self.refresh_song_list(&mut songs_by_artist); + let songs_by_artist = self.songs_by_artist.lock().unwrap(); + self.refresh_artist_tree(&songs_by_artist); + self.refresh_song_list(&songs_by_artist); } fn refresh_artist_tree(&self, songs_by_artist: &MutexGuard) { @@ -252,7 +237,11 @@ impl<'a> Library<'a> { }; let songs = if let Some(selected_album) = selected_album { - songs.iter().filter(|s| s.album.as_ref().is_some_and(|sa| *sa == selected_album)).cloned().collect() + songs + .iter() + .filter(|s| s.album.as_ref().is_some_and(|sa| *sa == selected_album)) + .cloned() + .collect() } else { songs.clone() }; @@ -268,11 +257,10 @@ impl<'a> Library<'a> { self.add_songs(songs); } - pub fn add_directory(&self, path: &PathBuf) { + pub fn add_directory(&self, path: &Path) { let songs = Song::from_dir(path); self.add_songs(songs); } - } impl Drop for Library<'_> { diff --git a/src/components/library/widget.rs b/src/components/library/widget.rs index d3d15a6..68c841f 100644 --- a/src/components/library/widget.rs +++ b/src/components/library/widget.rs @@ -1,8 +1,8 @@ use ratatui::{ - prelude::Widget, buffer::Buffer, layout::{Constraint, Layout, Rect}, - widgets::{WidgetRef}, + prelude::Widget, + widgets::WidgetRef, }; use super::Library; @@ -20,8 +20,8 @@ impl<'a> WidgetRef for Library<'a> { Constraint::Length(5), Constraint::Percentage(50), ]) - .horizontal_margin(2) - .areas(area); + .horizontal_margin(2) + .areas(area); self.album_tree.render_ref(area_left, buf); self.song_list.render_ref(area_right, buf); diff --git a/src/components/list.rs b/src/components/list.rs index 8ed340c..296eb2b 100644 --- a/src/components/list.rs +++ b/src/components/list.rs @@ -2,4 +2,4 @@ mod component; mod keyboard_handler; mod widget; -pub use component::{List, Direction}; +pub use component::{Direction, List}; diff --git a/src/components/list/component.rs b/src/components/list/component.rs index 6c80ab7..e9ce3a3 100644 --- a/src/components/list/component.rs +++ b/src/components/list/component.rs @@ -1,15 +1,11 @@ -use std::{ - sync::{ - atomic::{AtomicUsize, AtomicU8, Ordering}, - Mutex, - }, +use std::sync::{ + atomic::{AtomicU8, AtomicUsize, Ordering}, + Mutex, }; use crossterm::event::{KeyCode, KeyEvent}; -use crate::{ - config::Theme, -}; +use crate::config::Theme; #[derive(Eq, PartialEq)] pub enum Direction { @@ -35,7 +31,8 @@ pub struct ListItem { } pub struct List<'a, T: 'a> -where T: std::fmt::Display +where + T: std::fmt::Display, { pub(super) theme: Theme, @@ -63,13 +60,17 @@ where T: std::fmt::Display } impl<'a, T> List<'a, T> -where T: std::fmt::Display +where + T: std::fmt::Display, { pub fn new(theme: Theme, items: Vec) -> Self { - let items = items.into_iter().map(|item| ListItem { - inner: item, - is_match: false, - }).collect(); + let items = items + .into_iter() + .map(|item| ListItem { + inner: item, + is_match: false, + }) + .collect(); Self { theme, @@ -159,10 +160,13 @@ where T: std::fmt::Display pub fn set_items(&self, items: Vec) { self.selected_item_index.store(0, Ordering::SeqCst); self.offset.store(0, Ordering::SeqCst); - *self.items.lock().unwrap() = items.into_iter().map(|item| ListItem { - inner: item, - is_match: false, - }).collect(); + *self.items.lock().unwrap() = items + .into_iter() + .map(|item| ListItem { + inner: item, + is_match: false, + }) + .collect(); } pub fn push_item(&self, item: T) { @@ -176,7 +180,7 @@ where T: std::fmt::Display pub fn filter_mut(&self, cb: impl FnOnce(&mut String)) { let mut filter = self.filter.lock().unwrap(); - cb(&mut *filter); + cb(&mut filter); let mut items = self.items.lock().unwrap(); @@ -188,7 +192,11 @@ where T: std::fmt::Display if filter.is_empty() { item.is_match = false; } else { - item.is_match = item.inner.to_string().to_lowercase().contains(filter.to_lowercase().as_str()); + item.is_match = item + .inner + .to_string() + .to_lowercase() + .contains(filter.to_lowercase().as_str()); } } @@ -202,7 +210,6 @@ where T: std::fmt::Display } } } - } impl Drop for List<'_, T> { diff --git a/src/components/list/keyboard_handler.rs b/src/components/list/keyboard_handler.rs index 9bb900a..3c0ce67 100644 --- a/src/components/list/keyboard_handler.rs +++ b/src/components/list/keyboard_handler.rs @@ -1,18 +1,15 @@ -use std::sync::{ - atomic::Ordering, - MutexGuard, -}; +use std::sync::{atomic::Ordering, MutexGuard}; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; -use crate::{ui::KeyboardHandlerRef}; +use crate::ui::KeyboardHandlerRef; use super::component::{Direction, List}; impl<'a, T> KeyboardHandlerRef<'a> for List<'a, T> -where T: 'a + Clone + std::fmt::Display +where + T: 'a + Clone + std::fmt::Display, { - fn on_key(&self, key: KeyEvent) { let target = "::List.on_key"; log::trace!(target: target, "{:?}", key); @@ -25,9 +22,9 @@ where T: 'a + Clone + std::fmt::Display } match key.code { - KeyCode::Up | KeyCode::Down | KeyCode::Home | KeyCode::End | KeyCode::PageUp | KeyCode::PageDown => { + KeyCode::Up | KeyCode::Down | KeyCode::Home | KeyCode::End | KeyCode::PageUp | KeyCode::PageDown => { self.on_directional_key(key); - }, + } KeyCode::Enter => { self.filter_mut(|filter| { filter.clear(); @@ -52,8 +49,7 @@ where T: 'a + Clone + std::fmt::Display self.on_directional_key(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE)); } } - - }, + } KeyCode::Insert => { let f = self.on_insert_fn.lock().unwrap(); let Some(f) = &*f else { @@ -76,13 +72,14 @@ where T: 'a + Clone + std::fmt::Display let removed_item = items.remove(i); if i >= items.len() { - self.selected_item_index.store(items.len().saturating_sub(1), Ordering::Release); + self.selected_item_index + .store(items.len().saturating_sub(1), Ordering::Release); } drop(items); on_delete(removed_item.inner, i); - }, + } KeyCode::Char('r') if key.modifiers == KeyModifiers::CONTROL => { if self.on_rename_fn.lock().unwrap().is_none() { return; @@ -90,18 +87,18 @@ where T: 'a + Clone + std::fmt::Display *rename = self.with_selected_item(|item| Some(item.to_string())); drop(rename); self.on_request_focus_trap_fn.lock().unwrap()(true); - }, + } KeyCode::Char(char) => { self.filter_mut(|filter| { filter.push(char); }); - }, + } KeyCode::Esc => { self.filter_mut(|filter| { filter.clear(); }); - }, - _ => {}, + } + _ => {} } } } @@ -115,7 +112,8 @@ fn is_key_dir_downwards(key_code: KeyCode) -> bool { } impl<'a, T> List<'a, T> -where T: std::fmt::Display + Clone +where + T: std::fmt::Display + Clone, { fn on_directional_key(&self, key: KeyEvent) { let is_filtering = !self.filter.lock().unwrap().is_empty(); @@ -153,14 +151,12 @@ where T: std::fmt::Display + Clone } else { i -= 1; } - } else { - if is_filtering { - if let Some(n) = items.iter().skip(i as usize + 1).position(|item| item.is_match) { - i += n as i32 + 1; - } - } else { - i += 1; + } else if is_filtering { + if let Some(n) = items.iter().skip(i as usize + 1).position(|item| item.is_match) { + i += n as i32 + 1; } + } else { + i += 1; } } else if key.modifiers == KeyModifiers::ALT { if let Some(next_item_special) = &*self.find_next_item_by_fn.lock().unwrap() { @@ -185,11 +181,10 @@ where T: std::fmt::Display + Clone swapped = Some((i as usize, nexti as usize)); i = nexti; } - } else { return; } - }, + } KeyCode::PageUp if !is_filtering => { i -= page_size; } @@ -204,7 +199,7 @@ where T: std::fmt::Display + Clone } else { i = 0; } - }, + } KeyCode::End => { if is_filtering { if let Some(n) = items.iter().rposition(|item| item.is_match) { @@ -213,8 +208,8 @@ where T: std::fmt::Display + Clone } else { i = length - 1; } - }, - _ => {}, + } + _ => {} } if i == initial_i { @@ -225,7 +220,9 @@ where T: std::fmt::Display + Clone self.selected_item_index.store(i as usize, Ordering::SeqCst); let offset = self.offset.load(Ordering::Acquire) as i32; - if (is_key_dir_upwards(key.code) && i < offset + padding) || (is_key_dir_downwards(key.code) && i > offset + padding) { + if (is_key_dir_upwards(key.code) && i < offset + padding) + || (is_key_dir_downwards(key.code) && i > offset + padding) + { let offset = if i > padding { (i - padding).min(length - height).max(0) } else { @@ -256,18 +253,18 @@ where T: std::fmt::Display + Clone match key.code { KeyCode::Char(char) => { rename.push(char); - }, + } KeyCode::Backspace => { if key.modifiers == KeyModifiers::ALT { rename.clear(); - } else if rename.len() > 0 { + } else if !rename.is_empty() { rename.remove(rename.len().saturating_sub(1)); } - }, + } KeyCode::Esc => { *rename_opt = None; self.on_request_focus_trap_fn.lock().unwrap()(false); - }, + } KeyCode::Enter => { if rename.is_empty() { return; @@ -287,5 +284,4 @@ where T: std::fmt::Display + Clone _ => {} } } - } diff --git a/src/components/list/widget.rs b/src/components/list/widget.rs index 7401d85..b1a36e4 100644 --- a/src/components/list/widget.rs +++ b/src/components/list/widget.rs @@ -8,7 +8,7 @@ use ratatui::{ buffer::Buffer, layout::Rect, style::Style, - widgets::{WidgetRef, Widget}, + widgets::{Widget, WidgetRef}, }; use super::component::List; @@ -28,9 +28,13 @@ impl<'a> Widget for ListLine<'a> { Style::default().fg(self.theme.background).bg(self.theme.search) } else if self.is_selected { if self.list_has_focus { - Style::default().fg(self.theme.foreground_selected).bg(self.theme.background_selected) + Style::default() + .fg(self.theme.foreground_selected) + .bg(self.theme.background_selected) } else { - Style::default().fg(self.theme.foreground_selected).bg(self.theme.background_selected_blur) + Style::default() + .fg(self.theme.foreground_selected) + .bg(self.theme.background_selected_blur) } } else { let fg = if self.is_match { @@ -43,14 +47,10 @@ impl<'a> Widget for ListLine<'a> { let line: Cow<'a, str> = if self.is_renaming { let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis(); - let caret = if now % 500 < 250 { - '⎸' - } else { - ' ' - }; + let caret = if now % 500 < 250 { '⎸' } else { ' ' }; format!("{}{}", self.text, caret).into() } else { - self.text.into() + self.text }; let line = ratatui::text::Line::from(line).style(style); @@ -59,7 +59,8 @@ impl<'a> Widget for ListLine<'a> { } impl<'a, T> WidgetRef for List<'a, T> -where T: std::fmt::Display, +where + T: std::fmt::Display, { fn render_ref(&self, area: Rect, buf: &mut Buffer) { self.height.store(area.height as usize, Ordering::Relaxed); @@ -108,7 +109,6 @@ where T: std::fmt::Display, }; line.render(area, buf); - } } } diff --git a/src/components/playlists.rs b/src/components/playlists.rs index 4cd4974..86c43dd 100644 --- a/src/components/playlists.rs +++ b/src/components/playlists.rs @@ -1,5 +1,5 @@ +mod keyboard_handler; mod playlists; mod widget; -mod keyboard_handler; pub use playlists::*; diff --git a/src/components/playlists/keyboard_handler.rs b/src/components/playlists/keyboard_handler.rs index 7d9d67e..11afe00 100644 --- a/src/components/playlists/keyboard_handler.rs +++ b/src/components/playlists/keyboard_handler.rs @@ -1,14 +1,10 @@ use crossterm::event::{KeyCode, KeyEvent}; -use crate::{ - components::playlists::playlists::PlaylistScreenElement, - ui::KeyboardHandlerRef, -}; +use crate::{components::playlists::playlists::PlaylistScreenElement, ui::KeyboardHandlerRef}; use super::Playlists; impl<'a> KeyboardHandlerRef<'a> for Playlists<'a> { - fn on_key(&self, key: KeyEvent) { let mut focused_element_guard = self.focused_element.lock().unwrap(); @@ -22,14 +18,13 @@ impl<'a> KeyboardHandlerRef<'a> for Playlists<'a> { PlaylistScreenElement::SongList => PlaylistScreenElement::PlaylistList, }; } - _ if *focused_element_guard == PlaylistScreenElement::PlaylistList => { + _ if *focused_element_guard == PlaylistScreenElement::PlaylistList => { self.playlist_list.on_key(key); - }, - _ if *focused_element_guard == PlaylistScreenElement::SongList => { + } + _ if *focused_element_guard == PlaylistScreenElement::SongList => { self.song_list.on_key(key); - }, - _ => {}, + } + _ => {} } } - } diff --git a/src/components/playlists/playlists.rs b/src/components/playlists/playlists.rs index 8ac5acb..c5fdc19 100644 --- a/src/components/playlists/playlists.rs +++ b/src/components/playlists/playlists.rs @@ -1,17 +1,12 @@ -use std::{ - sync::Mutex, - rc::Rc, - cell::Cell, -}; +use std::{cell::Cell, rc::Rc, sync::Mutex}; use chrono::Local; -use crossterm::event::{KeyEvent}; use crate::{ - structs::{Song, Playlist}, + components::List, config::Theme, cue::CueSheet, - components::List, + structs::{Playlist, Song}, }; #[derive(Eq, PartialEq)] @@ -33,7 +28,14 @@ impl<'a> Playlists<'a> { pub fn new(theme: Theme) -> Self { let playlists_file = crate::files::Playlists::from_file(); - let song_list = Rc::new(List::new(theme, playlists_file.playlists.get(0).map(|pl| pl.songs.clone()).unwrap_or(vec![]))); + let song_list = Rc::new(List::new( + theme, + playlists_file + .playlists + .first() + .map(|pl| pl.songs.clone()) + .unwrap_or_default(), + )); let playlist_list = Rc::new(List::new(theme, playlists_file.playlists)); let deleted_playlist_list = Rc::new(List::new(theme, playlists_file.deleted)); @@ -60,9 +62,10 @@ impl<'a> Playlists<'a> { let playlist_list = playlist_list.clone(); let deleted_playlist_list = deleted_playlist_list.clone(); move || { - let playlist = Playlist::new( - format!("New playlist created at {}", Local::now().format("%A %-l:%M:%S%P")), - ); + let playlist = Playlist::new(format!( + "New playlist created at {}", + Local::now().format("%A %-l:%M:%S%P") + )); playlist_list.push_item(playlist); save(&playlist_list, &deleted_playlist_list); } diff --git a/src/components/playlists/widget.rs b/src/components/playlists/widget.rs index c6ff90c..4f2c3f1 100644 --- a/src/components/playlists/widget.rs +++ b/src/components/playlists/widget.rs @@ -1,24 +1,27 @@ -use std::{ - fmt::{Display, Formatter}, -}; +use std::fmt::{Display, Formatter}; use ratatui::{ - prelude::Alignment, buffer::Buffer, layout::{Constraint, Layout, Rect}, - widgets::WidgetRef, + prelude::Alignment, style::Style, + widgets::WidgetRef, }; use super::Playlists; impl Display for crate::structs::Song { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{} - {} - {} - {}", - self.year.as_ref().map(|y| y.to_string()).unwrap_or("(no year)".to_string()), - self.album.clone().unwrap_or("(no album)".to_string()), - self.track.unwrap_or(0), - self.title.clone() + write!( + f, + "{} - {} - {} - {}", + self.year + .as_ref() + .map(|y| y.to_string()) + .unwrap_or("(no year)".to_string()), + self.album.clone().unwrap_or("(no album)".to_string()), + self.track.unwrap_or(0), + self.title.clone() ) } } @@ -36,8 +39,8 @@ impl<'a> WidgetRef for Playlists<'a> { Constraint::Length(5), Constraint::Percentage(50), ]) - .horizontal_margin(2) - .areas(area); + .horizontal_margin(2) + .areas(area); let show_deleted_playlists = self.show_deleted_playlists.get(); @@ -49,7 +52,7 @@ impl<'a> WidgetRef for Playlists<'a> { Constraint::Length(1), Constraint::Percentage(50), ]) - .areas(area_left); + .areas(area_left); self.playlist_list.render_ref(left_top, buf); diff --git a/src/components/queue.rs b/src/components/queue.rs index 0942708..2e01ce6 100644 --- a/src/components/queue.rs +++ b/src/components/queue.rs @@ -1,5 +1,5 @@ -pub mod widget; pub mod keyboard_handler; pub mod queue; +pub mod widget; pub use queue::Queue; diff --git a/src/components/queue/queue.rs b/src/components/queue/queue.rs index cf902d9..32c2f87 100644 --- a/src/components/queue/queue.rs +++ b/src/components/queue/queue.rs @@ -1,14 +1,13 @@ -use std::sync::{ - Arc, Condvar, Mutex, MutexGuard, - atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering}, +use std::{ + collections::VecDeque, + sync::{ + atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering}, + Arc, Condvar, Mutex, MutexGuard, + }, + time::Duration, }; -use std::time::Duration; -use std::collections::VecDeque; -use crate::{ - config::Theme, - structs::Song, -}; +use crate::{config::Theme, structs::Song}; pub struct Queue { pub(super) theme: Theme, @@ -82,7 +81,7 @@ impl Queue { log::trace!(target: "::queue.mut_queue", "acquiring lock on songs"); let mut songs = self.songs(); - f(&mut *songs); + f(&mut songs); self.queue_length.store(songs.len(), Ordering::SeqCst); self.set_total_time(song_list_to_duration(&songs).as_secs()); @@ -112,7 +111,7 @@ impl Queue { pub fn selected_song(&self) -> Option { let songs = self.songs(); - songs.get(self.selected_song_index()).map(|s| s.clone()) + songs.get(self.selected_song_index()).cloned() } pub fn select_next(&self) { @@ -123,7 +122,8 @@ impl Queue { }; self.selected_item_index.fetch_add(1, Ordering::SeqCst); - self.selected_item_index.fetch_min(length.saturating_sub(1), Ordering::SeqCst); + self.selected_item_index + .fetch_min(length.saturating_sub(1), Ordering::SeqCst); } pub fn select_previous(&self) { @@ -134,7 +134,8 @@ impl Queue { }; self.selected_item_index.fetch_sub(1, Ordering::SeqCst); - self.selected_item_index.fetch_min(length.saturating_sub(1), Ordering::SeqCst); + self.selected_item_index + .fetch_min(length.saturating_sub(1), Ordering::SeqCst); } pub fn add_front(&self, song: Song) { diff --git a/src/components/queue/widget.rs b/src/components/queue/widget.rs index ad68238..0a6c217 100644 --- a/src/components/queue/widget.rs +++ b/src/components/queue/widget.rs @@ -9,9 +9,7 @@ use super::queue::Queue; impl WidgetRef for Queue { fn render_ref(&self, area: Rect, buf: &mut Buffer) { - let [area] = Layout::horizontal([ - Constraint::Percentage(100), - ]) + let [area] = Layout::horizontal([Constraint::Percentage(100)]) .horizontal_margin(2) .areas(area); @@ -31,7 +29,7 @@ impl WidgetRef for Queue { queue_list, area, buf, - &mut ListState::default().with_selected(Some(self.selected_song_index())) + &mut ListState::default().with_selected(Some(self.selected_song_index())), ); } } diff --git a/src/components/rendering_error.rs b/src/components/rendering_error.rs index 432434f..7a7208b 100644 --- a/src/components/rendering_error.rs +++ b/src/components/rendering_error.rs @@ -16,15 +16,18 @@ impl WidgetRef for RenderingError { .style(Style::new().bg(Color::Rgb(255, 0, 0))) .borders(ratatui::widgets::Borders::ALL) .border_style(Style::new().fg(Color::Rgb(255, 255, 255))) - .render_ref(area.clone(), buf); + .render_ref(area, buf); let [_, area_center, _] = Layout::vertical([ Constraint::Percentage(50), Constraint::Length(1), Constraint::Percentage(50), ]) - .areas(area); + .areas(area); - ratatui::text::Line::from("RENDERING ERROR").style(Style::new().fg(Color::Rgb(255, 255, 255))).centered().render_ref(area_center, buf); + ratatui::text::Line::from("RENDERING ERROR") + .style(Style::new().fg(Color::Rgb(255, 255, 255))) + .centered() + .render_ref(area_center, buf); } } diff --git a/src/cue/cue_line.rs b/src/cue/cue_line.rs index 96955df..28991e3 100644 --- a/src/cue/cue_line.rs +++ b/src/cue/cue_line.rs @@ -75,7 +75,7 @@ mod tests { #[test] fn cue_lines_from_file() { let path = Path::new("./src/cue/Tim Buckley - Happy Sad.cue"); - let cue_lines = CueLine::from_file(&path).unwrap(); + let cue_lines = CueLine::from_file(path).unwrap(); assert_eq!(cue_lines.len(), 31, "{:#?}", cue_lines); diff --git a/src/cue/cue_line_node.rs b/src/cue/cue_line_node.rs index d92bbc5..7b7332e 100644 --- a/src/cue/cue_line_node.rs +++ b/src/cue/cue_line_node.rs @@ -70,17 +70,15 @@ impl CueLineNode { node.children.append(&mut tmp_children); stack.push(node); break; + } else if tmp_children.is_empty() { + depth = node.line.as_ref().unwrap().indentation; + tmp_children.push(node); + } else if node.line.as_ref().unwrap().indentation == depth { + tmp_children.push(node); } else { - if tmp_children.is_empty() { - depth = node.line.as_ref().unwrap().indentation; - tmp_children.push(node); - } else if node.line.as_ref().unwrap().indentation == depth { - tmp_children.push(node); - } else { - tmp_children.reverse(); - node.children.append(&mut tmp_children); - stack.push(node); - } + tmp_children.reverse(); + node.children.append(&mut tmp_children); + stack.push(node); } } } @@ -98,7 +96,7 @@ mod tests { #[test] fn cue_line_nodes_from_lines() { let path = Path::new("src/cue/Tim Buckley - Happy Sad.cue"); - let cue_lines = CueLine::from_file(&path).unwrap(); + let cue_lines = CueLine::from_file(path).unwrap(); let cue_nodes = CueLineNode::from_lines(VecDeque::from(cue_lines)); diff --git a/src/cue/cue_sheet.rs b/src/cue/cue_sheet.rs index 6628c04..d9fd1a9 100644 --- a/src/cue/cue_sheet.rs +++ b/src/cue/cue_sheet.rs @@ -8,7 +8,7 @@ use crate::cue::cue_line_node::CueLineNode; use crate::cue::cue_sheet_item::CueSheetItem; #[allow(dead_code)] -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, Eq, PartialEq)] pub struct CueSheet { cue_sheet_file_path: PathBuf, unknown: Vec, @@ -66,9 +66,9 @@ impl Track { while let Some(t) = track_properties.pop() { match t { - CueSheetItem::Title(s) => { track.title = s } - CueSheetItem::Performer(s) => { track.performer = Some(s) } - CueSheetItem::Index(s) => { track.start_time = s } + CueSheetItem::Title(s) => track.title = s, + CueSheetItem::Performer(s) => track.performer = Some(s), + CueSheetItem::Index(s) => track.start_time = s, _ => {} } } @@ -108,27 +108,24 @@ impl Track { multiplier *= 60; } - let duration = Duration::from_secs(seconds); - - duration + Duration::from_secs(seconds) } } impl CueSheet { pub fn from_file(path: &Path) -> io::Result { - let cue_lines = CueLine::from_file(&path)?; + let cue_lines = CueLine::from_file(path)?; let cue_nodes = CueLineNode::from_lines(VecDeque::from(cue_lines)); - let mut top_cue_items: Vec = - cue_nodes.iter().map(|n| CueSheetItem::from_cue_line_node(n)).collect(); + let mut top_cue_items: Vec = cue_nodes.iter().map(CueSheetItem::from_cue_line_node).collect(); let mut sheet = CueSheet::default(); sheet.cue_sheet_file_path = path.to_path_buf(); while let Some(e) = top_cue_items.pop() { match e { - CueSheetItem::Comment(s) => { sheet.comments.push(s) } - CueSheetItem::Title(s) => { sheet.title = Some(s) } - CueSheetItem::Performer(s) => { sheet.performer = Some(s) } + CueSheetItem::Comment(s) => sheet.comments.push(s), + CueSheetItem::Title(s) => sheet.title = Some(s), + CueSheetItem::Performer(s) => sheet.performer = Some(s), CueSheetItem::File(s, c) => { sheet.file = Some(CueFile::new(s, c)); } @@ -165,7 +162,7 @@ mod tests { #[test] fn cue_sheet_from_file() { let path = Path::new("./src/cue/Tim Buckley - Happy Sad.cue"); - let cue = CueSheet::from_file(&path).unwrap(); + let cue = CueSheet::from_file(path).unwrap(); assert_eq!(cue.unknown.len(), 0); assert_eq!(cue.comments.len(), 4); diff --git a/src/cue/cue_sheet_item.rs b/src/cue/cue_sheet_item.rs index b1c3cdc..92ffa23 100644 --- a/src/cue/cue_sheet_item.rs +++ b/src/cue/cue_sheet_item.rs @@ -24,19 +24,11 @@ impl CueSheetItem { "TITLE" => Self::Title(line.value.strip_quotes().to_string()), "INDEX" => Self::Index(line.value.clone()), "FILE" => { - let children = cue_line_node - .children - .iter() - .map(|n| Self::from_cue_line_node(n)) - .collect(); + let children = cue_line_node.children.iter().map(Self::from_cue_line_node).collect(); Self::File(line.value.clone(), children) } "TRACK" => { - let children = cue_line_node - .children - .iter() - .map(|n| Self::from_cue_line_node(n)) - .collect(); + let children = cue_line_node.children.iter().map(Self::from_cue_line_node).collect(); Self::Track(line.value.clone(), children) } _ => Self::Unknown, @@ -51,9 +43,9 @@ mod tests { use std::collections::VecDeque; use std::path::Path; + use super::*; use crate::cue::cue_line::CueLine; use crate::cue::cue_line_node::CueLineNode; - use super::*; #[test] fn cue_sheet_item_from_cue_line_node() { @@ -87,10 +79,9 @@ mod tests { #[test] fn cue_sheet_items_from_file() { let path = Path::new("src/cue/Tim Buckley - Happy Sad.cue"); - let cue_lines = CueLine::from_file(&path).unwrap(); + let cue_lines = CueLine::from_file(path).unwrap(); let cue_nodes = CueLineNode::from_lines(VecDeque::from(cue_lines)); - - let top_cue_items: Vec = cue_nodes.iter().map(|n| CueSheetItem::from_cue_line_node(n)).collect(); + let top_cue_items: Vec = cue_nodes.iter().map(CueSheetItem::from_cue_line_node).collect(); assert_eq!(top_cue_items.len(), 7); diff --git a/src/files/library.rs b/src/files/library.rs index 366a496..0ae8866 100644 --- a/src/files/library.rs +++ b/src/files/library.rs @@ -7,19 +7,11 @@ use crate::{ toml::{read_toml_file_or_default, write_toml_file, TomlFileError}, }; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Default)] pub struct Library { pub songs_by_artist: HashMap>, } -impl Default for Library { - fn default() -> Self { - Self { - songs_by_artist: HashMap::new(), - } - } -} - impl Library { pub fn from_file() -> Self { read_toml_file_or_default("library") @@ -42,7 +34,7 @@ impl Library { continue; }; - let artist_songs = self.songs_by_artist.entry(artist.clone()).or_insert(vec![]); + let artist_songs = self.songs_by_artist.entry(artist.clone()).or_default(); if let Err(i) = artist_songs.binary_search(&song) { artist_songs.insert(i, song); } @@ -64,5 +56,4 @@ impl Library { artist_songs.retain(|s| s.album.as_ref().is_some_and(|a| *a != album)); self.save(); } - } diff --git a/src/files/playlists.rs b/src/files/playlists.rs index 42eb941..741eb93 100644 --- a/src/files/playlists.rs +++ b/src/files/playlists.rs @@ -5,21 +5,12 @@ use crate::{ toml::{read_toml_file_or_default, write_toml_file, TomlFileError}, }; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Default)] pub struct Playlists { pub playlists: Vec, pub deleted: Vec, } -impl Default for Playlists { - fn default() -> Self { - Self { - playlists: vec![], - deleted: vec![], - } - } -} - impl Playlists { pub fn from_file() -> Self { read_toml_file_or_default("playlists") diff --git a/src/main.rs b/src/main.rs index 72f7685..b764dae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,21 +1,23 @@ +#![allow(clippy::field_reassign_with_default)] + mod app; +mod auto_update; +mod bye; +mod components; mod config; mod constants; mod cue; mod extensions; +mod files; mod mpris; mod player; +mod source; +mod spawn_terminal; mod state; mod structs; mod term; mod toml; mod ui; -mod source; -mod components; -mod bye; -mod files; -mod auto_update; -mod spawn_terminal; use std::error::Error; use std::io::stdout; @@ -24,7 +26,7 @@ use std::thread; use async_std::task; use colored::{Color, Colorize}; -use flexi_logger::{DeferredNow, FileSpec, Logger, WriteMode, style}; +use flexi_logger::{style, DeferredNow, FileSpec, Logger, WriteMode}; use futures::{ future::FutureExt, // for `.fuse()` pin_mut, @@ -32,13 +34,7 @@ use futures::{ }; use log::{debug, error, info, Record}; -use crate::{ - app::App, - mpris::create_mpris_player, - term::reset_terminal, - bye::bye, - auto_update::auto_update, -}; +use crate::{app::App, auto_update::auto_update, bye::bye, mpris::create_mpris_player, term::reset_terminal}; pub enum Command { PlayPause, @@ -47,7 +43,7 @@ pub enum Command { } pub fn log_format(w: &mut dyn std::io::Write, now: &mut DeferredNow, record: &Record) -> Result<(), std::io::Error> { - write!(w, "{} ", now.format("%-l:%M:%S%P").to_string())?; + write!(w, "{} ", now.format("%-l:%M:%S%P"))?; let level = format!("{: <8}", record.level()); write!(w, "{}", style(record.level()).paint(level))?; @@ -113,11 +109,9 @@ async fn main() -> Result<(), Box> { select! { _ = task_player => { log::trace!("player task finish"); - () }, _ = task_mpris => { log::trace!("mpris task finish"); - () }, } @@ -136,7 +130,7 @@ fn set_panic_hook() { std::panic::set_hook(Box::new(move |panic_info| { // intentionally ignore errors here since we're already in a panic - let _ = reset_terminal(&mut stdout()); + reset_terminal(&mut stdout()); original_hook(panic_info); })); } diff --git a/src/mpris.rs b/src/mpris.rs index 017013a..1c799fe 100644 --- a/src/mpris.rs +++ b/src/mpris.rs @@ -3,7 +3,9 @@ use std::sync::mpsc::Sender; use crate::Command; -pub async fn create_mpris_player(player_command_sender: Sender) -> Result> { +pub async fn create_mpris_player( + player_command_sender: Sender, +) -> Result> { let player = mpris_server::Player::builder("com.taro-codes.jolteon") .can_play(true) .can_pause(true) @@ -30,7 +32,7 @@ pub async fn create_mpris_player(player_command_sender: Sender) -> Resu }); player.connect_quit(|_player| { - log::trace!("mpris quit"); + log::trace!("mpris quit"); }); player.connect_stop({ diff --git a/src/player.rs b/src/player.rs index feb7b16..5d9f07b 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,9 +1,8 @@ use std::{ sync::{ - Arc, - Mutex, atomic::{AtomicBool, AtomicU64, Ordering}, mpsc::{channel, Receiver, RecvTimeoutError, Sender}, + Arc, Mutex, }, thread, thread::JoinHandle, @@ -11,20 +10,15 @@ use std::{ }; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; -use ratatui::{ - buffer::Buffer, - layout::Rect, - prelude::Widget, - widgets::WidgetRef, -}; +use ratatui::{buffer::Buffer, layout::Rect, prelude::Widget, widgets::WidgetRef}; use rodio::OutputStreamHandle; use crate::{ - structs::{Action, OnAction, PlayerAction, Song}, - source::{Source, Controls}, - ui::{KeyboardHandlerRef, CurrentlyPlaying}, - config::Theme, components::Queue, + config::Theme, + source::{Controls, Source}, + structs::{Action, OnAction, PlayerAction, Song}, + ui::{CurrentlyPlaying, KeyboardHandlerRef}, }; pub struct Player { @@ -34,7 +28,7 @@ pub struct Player { queue_items: Arc, currently_playing: Arc>>, currently_playing_start_time: Arc, - command_sender: Option>, + command_sender: Sender, command_receiver: Arc>>>, is_stopped: Arc, volume: Arc>, @@ -67,7 +61,7 @@ impl Player { queue_items: queue, currently_playing: Arc::new(Mutex::new(None)), currently_playing_start_time: Arc::new(AtomicU64::new(0)), - command_sender: Some(command_sender), + command_sender, command_receiver: Arc::new(Mutex::new(Some(command_receiver))), is_stopped: Arc::new(AtomicBool::new(true)), volume: Arc::new(Mutex::new(1.0)), @@ -81,11 +75,9 @@ impl Player { } fn send_command(&self, command: Command) { - self.command_sender.as_ref().map(|tx| { - if let Err(err) = tx.send(command) { - log::warn!("Player.send_command() failure: {:?}", err); - } - }); + if let Err(err) = self.command_sender.send(command) { + log::warn!("Player.send_command() failure: {:?}", err); + } } pub fn get_pos(&self) -> Duration { @@ -117,7 +109,7 @@ impl Player { let set_currently_playing = move |song: Option| { let start_time = song .as_ref() - .and_then(|song| Some(song.start_time)) + .map(|song| song.start_time) .unwrap_or(Duration::ZERO) .as_secs(); song_start_time.store(start_time, Ordering::Relaxed); @@ -132,180 +124,181 @@ impl Player { }; }; - let thread = thread::Builder::new().name("player".to_string()).spawn(move || { - while let Ok(song) = queue_items.pop() { - let path = song.path.clone(); - let start_time = song.start_time.clone(); - let length = song.length.clone(); - - is_stopped.store(false, Ordering::SeqCst); - - set_currently_playing(Some(song)); - - let periodic_access = { - let is_stopped = is_stopped.clone(); - let must_stop = must_stop.clone(); - let volume = volume.clone(); - let pause = pause.clone(); - let must_seek = must_seek.clone(); - - move |controls: &mut Controls| { - if must_stop.swap(false, Ordering::SeqCst) { - controls.stop(); - controls.skip(); - is_stopped.store(true, Ordering::SeqCst); - log::debug!("periodic access stop"); - return; - } + let thread = thread::Builder::new() + .name("player".to_string()) + .spawn(move || { + while let Ok(song) = queue_items.pop() { + let path = song.path.clone(); + let start_time = song.start_time; + let length = song.length; + + is_stopped.store(false, Ordering::SeqCst); + + set_currently_playing(Some(song)); + + let periodic_access = { + let is_stopped = is_stopped.clone(); + let must_stop = must_stop.clone(); + let volume = volume.clone(); + let pause = pause.clone(); + let must_seek = must_seek.clone(); + + move |controls: &mut Controls| { + if must_stop.swap(false, Ordering::SeqCst) { + controls.stop(); + controls.skip(); + is_stopped.store(true, Ordering::SeqCst); + log::debug!("periodic access stop"); + return; + } - controls.set_volume(*volume.lock().unwrap()); - controls.set_paused(pause.load(Ordering::SeqCst)); + controls.set_volume(*volume.lock().unwrap()); + controls.set_paused(pause.load(Ordering::SeqCst)); - if let Some(seek) = must_seek.lock().unwrap().take() { - if let Err(err) = controls.seek(seek) { - log::error!("periodic_access.try_seek() error. {:?}", err) + if let Some(seek) = must_seek.lock().unwrap().take() { + if let Err(err) = controls.seek(seek) { + log::error!("periodic_access.try_seek() error. {:?}", err) + } } } - } - }; - - let wait_until_song_ends = || { - let target = "::wait_until_song_ends"; - log::debug!(target: target, "start"); - must_stop.store(true, Ordering::SeqCst); - - if let Err(err) = song_ended_rx.recv() { - log::error!("ender_recv.recv {:?}", err); - return; - } - - log::debug!(target: target, "ender signal received"); - - while song_ended_rx.try_recv().is_ok() {} + }; - must_stop.store(false, Ordering::SeqCst); - must_seek.lock().unwrap().take(); + let wait_until_song_ends = || { + let target = "::wait_until_song_ends"; + log::debug!(target: target, "start"); + must_stop.store(true, Ordering::SeqCst); - set_currently_playing(None); + if let Err(err) = song_ended_rx.recv() { + log::error!("ender_recv.recv {:?}", err); + return; + } - log::debug!(target: target, "done"); - }; + log::debug!(target: target, "ender signal received"); + while song_ended_rx.try_recv().is_ok() {} - let mut source = Source::from_file(path, periodic_access, position.clone(), { - let song_ended_tx = song_ended_tx.clone(); - move || { - log::trace!("source.on_playback_ended"); - let _ = song_ended_tx.send(()); - } - }); + must_stop.store(false, Ordering::SeqCst); + must_seek.lock().unwrap().take(); - if start_time > Duration::ZERO { - log::debug!("start_time > Duration::ZERO, {:?}", start_time); - if let Err(err) = source.seek(start_time) { - log::error!("start_time > 0 try_seek() error. {:?}", err) - } - } + set_currently_playing(None); - *position.lock().unwrap() = start_time; + log::debug!(target: target, "done"); + }; - log::debug!("output_stream.play_raw()"); - if let Err(err) = output_stream.play_raw(source) { // Does `mixer.add(source)`. Mixer is tied to the CPAL thread, which starts consuming the source automatically. - log::error!("os.play_raw error! {:?}", err); - continue; - } + let mut source = Source::from_file(path, periodic_access, position.clone(), { + let song_ended_tx = song_ended_tx.clone(); + move || { + log::trace!("source.on_playback_ended"); + let _ = song_ended_tx.send(()); + } + }); - // Start looping until the current song ends OR something wakes us up. - // When woken up, we check whether we need to immediately exit. - // If we don't, we recalculate the remaining time until the song ends, - // and then go back to bed. - loop { - let sleepy_time = if pause.load(Ordering::SeqCst) { - Duration::MAX - } else { - let abs_pos = position.lock().unwrap().saturating_sub(start_time); - if abs_pos >= length { - log::debug!("inner loop: pos >= length, {:?} > {:?}", abs_pos, length); - break; + if start_time > Duration::ZERO { + log::debug!("start_time > Duration::ZERO, {:?}", start_time); + if let Err(err) = source.seek(start_time) { + log::error!("start_time > 0 try_seek() error. {:?}", err) } - length - abs_pos - }; + } - // log::debug!("inner loop: sleepy_time! {:?}", sleepy_time); + *position.lock().unwrap() = start_time; - match command_receiver.recv_timeout(sleepy_time) { - Ok(command) => { - log::debug!("Player.Command({:?})", command); - match command { - Command::Quit => { - log::trace!("Player: quitting main loop"); - return; - } - Command::Play => { - pause.store(false, Ordering::SeqCst); - } - Command::Pause => { - pause.store(true, Ordering::SeqCst); - } - Command::Stop => { - break; - } - Command::Seek(seek) => { - // NOTE: "intense" seek causes `ALSA lib pcm.c:8740:(snd_pcm_recover) underrun occurred`. - // See https://github.com/RustAudio/cpal/pull/909 + log::debug!("output_stream.play_raw()"); + if let Err(err) = output_stream.play_raw(source) { + // play_raw does `mixer.add(source)`. Mixer is tied to the CPAL thread, which starts consuming the source automatically. + log::error!("os.play_raw error! {:?}", err); + continue; + } - if seek == 0 { - log::error!("Command::Seek(0)"); - continue; + // Start looping until the current song ends OR something wakes us up. + // When woken up, we check whether we need to immediately exit. + // If we don't, we recalculate the remaining time until the song ends, + // and then go back to bed. + loop { + let sleepy_time = if pause.load(Ordering::SeqCst) { + Duration::MAX + } else { + let abs_pos = position.lock().unwrap().saturating_sub(start_time); + if abs_pos >= length { + log::debug!("inner loop: pos >= length, {:?} > {:?}", abs_pos, length); + break; + } + length - abs_pos + }; + + // log::debug!("inner loop: sleepy_time! {:?}", sleepy_time); + + match command_receiver.recv_timeout(sleepy_time) { + Ok(command) => { + log::debug!("Player.Command({:?})", command); + match command { + Command::Quit => { + log::trace!("Player: quitting main loop"); + return; } - - if is_stopped.load(Ordering::SeqCst) || must_stop.load(Ordering::SeqCst) { - continue; + Command::Play => { + pause.store(false, Ordering::SeqCst); } - - let seek_abs = Duration::from_secs(seek.abs() as u64); - let mut pos = position.lock().unwrap(); - - let target = if seek > 0 { - pos.saturating_add(seek_abs) - } else { - pos.saturating_sub(seek_abs).max(start_time) - }; - - // If we'd seek past song end, skip seeking and just move to next song instead. - if target > length + start_time { - log::debug!("Seeking past end"); + Command::Pause => { + pause.store(true, Ordering::SeqCst); + } + Command::Stop => { break; } - - log::debug!("Seek({:?})", target); - *must_seek.lock().unwrap() = Some(target); - *pos = target; // optimistic update, otherwise sleepy_time will be off - + Command::Seek(seek) => { + // NOTE: "intense" seek causes `ALSA lib pcm.c:8740:(snd_pcm_recover) underrun occurred`. + // See https://github.com/RustAudio/cpal/pull/909 + + if seek == 0 { + log::error!("Command::Seek(0)"); + continue; + } + + if is_stopped.load(Ordering::SeqCst) || must_stop.load(Ordering::SeqCst) { + continue; + } + + let seek_abs = Duration::from_secs(seek.unsigned_abs() as u64); + let mut pos = position.lock().unwrap(); + + let target = if seek > 0 { + pos.saturating_add(seek_abs) + } else { + pos.saturating_sub(seek_abs).max(start_time) + }; + + // If we'd seek past song end, skip seeking and just move to next song instead. + if target > length + start_time { + log::debug!("Seeking past end"); + break; + } + + log::debug!("Seek({:?})", target); + *must_seek.lock().unwrap() = Some(target); + *pos = target; // optimistic update, otherwise sleepy_time will be off + } } } - } - Err(RecvTimeoutError::Timeout) => { - // Playing song reached its end. We want to move on to the next song. - log::trace!("Player Command Timeout"); - break; - } - Err(RecvTimeoutError::Disconnected) => { - // Most of the time, not a real error. This can happen because the command_sender was dropped, - // which happens when the player itself was dropped, so we just want to exit. - log::warn!("RecvTimeoutError::Disconnected"); - return; + Err(RecvTimeoutError::Timeout) => { + // Playing song reached its end. We want to move on to the next song. + log::trace!("Player Command Timeout"); + break; + } + Err(RecvTimeoutError::Disconnected) => { + // Most of the time, not a real error. This can happen because the command_sender was dropped, + // which happens when the player itself was dropped, so we just want to exit. + log::warn!("RecvTimeoutError::Disconnected"); + return; + } } } - } - - while command_receiver.try_recv().is_ok() {} // "drain" the command queue - dropping everything that might have accumulated. - wait_until_song_ends(); + while command_receiver.try_recv().is_ok() {} // "drain" the command queue - dropping everything that might have accumulated. - } - log::trace!("Player loop exit"); - }).unwrap(); + wait_until_song_ends(); + } + log::trace!("Player loop exit"); + }) + .unwrap(); *self.main_thread.lock().unwrap() = Some(thread); } @@ -324,7 +317,8 @@ impl Player { self.send_command(Command::Play); } else { self.send_command(Command::Pause); - self.paused_animation_start_frame.store(self.frame.load(Ordering::Relaxed), Ordering::Relaxed); + self.paused_animation_start_frame + .store(self.frame.load(Ordering::Relaxed), Ordering::Relaxed); } } @@ -352,13 +346,8 @@ impl Player { } pub fn change_volume(&self, amount: f32) { - let mut volume = *self.volume.lock().unwrap() + amount; - if volume < 0. { - volume = 0.; - } else if volume > 1. { - volume = 1.; - } - *self.volume.lock().unwrap() = volume; + let mut volume = self.volume.lock().unwrap(); + *volume = (*volume + amount).clamp(0., 1.); } } @@ -392,8 +381,9 @@ impl WidgetRef for Player { let frame = self.frame.fetch_add(1, Ordering::Relaxed); let is_paused = self.pause.load(Ordering::Relaxed) && { - let step = (frame - self.paused_animation_start_frame.load(Ordering::Relaxed)) % (6 * 16); - (step < 6 * 8 && step % 12 < 6) || step >= 6 * 8 + const ANIM_LEN: u64 = 6 * 16; + let step = (frame - self.paused_animation_start_frame.load(Ordering::Relaxed)) % (ANIM_LEN); + step % 12 < 6 || step >= 6 * 8 // toggle visible/hidden every 6 frames, for half the length of the animation; then stay visible until the end. }; CurrentlyPlaying::new( @@ -403,7 +393,8 @@ impl WidgetRef for Player { self.queue_items.total_time(), self.queue_items.length(), is_paused, - ).render(area, buf); + ) + .render(area, buf); } } @@ -416,25 +407,34 @@ impl KeyboardHandlerRef<'_> for Player { KeyCode::Char('+') => self.change_volume(0.05), KeyCode::Char(' ') if key.modifiers == KeyModifiers::CONTROL => self.toggle(), KeyCode::Char('g') if key.modifiers == KeyModifiers::CONTROL => self.stop(), - _ => { }, + _ => {} }; } } impl OnAction for Player { fn on_action(&self, action: Action) { - match action { - Action::PlayerAction(action) => { - match action { - PlayerAction::PlayPause => {self.toggle();}, - PlayerAction::Stop => {self.stop();}, - PlayerAction::VolumeUp => {self.change_volume(0.05);}, - PlayerAction::VolumeDown => {self.change_volume(-0.05);}, - PlayerAction::SeekForwards => {self.seek_forward();}, - PlayerAction::SeekBackwards => {self.seek_backward();}, + if let Action::Player(action) = action { + match action { + PlayerAction::PlayPause => { + self.toggle(); + } + PlayerAction::Stop => { + self.stop(); + } + PlayerAction::VolumeUp => { + self.change_volume(0.05); + } + PlayerAction::VolumeDown => { + self.change_volume(-0.05); + } + PlayerAction::SeekForwards => { + self.seek_forward(); + } + PlayerAction::SeekBackwards => { + self.seek_backward(); } } - _ => {}, } } } diff --git a/src/source.rs b/src/source.rs index ae29bd6..229416c 100644 --- a/src/source.rs +++ b/src/source.rs @@ -5,9 +5,10 @@ use std::sync::{Arc, Mutex}; use std::time::Duration; use rodio::{ - Decoder, - Source as RodioSource, - source::{Amplify, Pausable, PeriodicAccess, SamplesConverter, Skippable, Speed, Stoppable, TrackPosition, SeekError}, + source::{ + Amplify, Pausable, PeriodicAccess, SamplesConverter, SeekError, Skippable, Speed, Stoppable, TrackPosition, + }, + Decoder, Source as RodioSource, }; type FullRodioSource = Stoppable>>>>>>>; @@ -19,7 +20,6 @@ pub struct Controls<'a> { } impl Controls<'_> { - #[inline] pub fn stop(&mut self) { self.src.stop(); @@ -73,11 +73,13 @@ impl Source<()> { mut periodic_access: impl FnMut(&mut Controls) + Send, shared_pos: Arc>, on_playback_end: impl FnOnce() + Send + 'static, - ) -> Source> - { + ) -> Source> { let periodic_access_inner = { Box::new(move |src: &mut FullRodioSource| { - let mut controls = Controls { src, shared_pos: &shared_pos }; + let mut controls = Controls { + src, + shared_pos: &shared_pos, + }; controls.refresh_pos(); periodic_access(&mut controls); }) @@ -106,7 +108,6 @@ impl Source where F: FnMut(&mut FullRodioSource) + Send, { - #[inline] pub fn _inner_mut(&mut self) -> &mut PeriodicRodioSource { &mut self.input @@ -117,7 +118,7 @@ where i.try_seek(pos) } - pub fn _skip(&mut self) -> () { + pub fn _skip(&mut self) { let i = self.input.inner_mut().inner_mut().inner_mut(); i.skip() } diff --git a/src/spawn_terminal.rs b/src/spawn_terminal.rs index b52109d..ad3579c 100644 --- a/src/spawn_terminal.rs +++ b/src/spawn_terminal.rs @@ -1,8 +1,4 @@ -use std::{ - path::PathBuf, - thread, - io::BufRead, -}; +use std::{io::BufRead, path::PathBuf, thread}; #[allow(dead_code)] fn spawn_terminal(cwd: PathBuf) { diff --git a/src/state.rs b/src/state.rs index 49ac698..0d692bb 100644 --- a/src/state.rs +++ b/src/state.rs @@ -5,22 +5,13 @@ use crate::{ toml::{read_toml_file_or_default, write_toml_file, TomlFileError}, }; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Default)] pub struct State { pub last_visited_path: Option, #[serde(default)] pub queue_items: Vec, } -impl Default for State { - fn default() -> Self { - Self { - last_visited_path: None, - queue_items: vec![], - } - } -} - impl State { pub fn from_file() -> Self { read_toml_file_or_default("state") diff --git a/src/structs.rs b/src/structs.rs index 327274e..e061992 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -1,9 +1,9 @@ -mod song; -mod playlist; -mod jolt; mod action; +mod jolt; +mod playlist; +mod song; -pub use song::Song; -pub use playlist::Playlist; -pub use jolt::Jolt; pub use action::*; +pub use jolt::Jolt; +pub use playlist::Playlist; +pub use song::Song; diff --git a/src/structs/action.rs b/src/structs/action.rs index 32e2b93..5281c49 100644 --- a/src/structs/action.rs +++ b/src/structs/action.rs @@ -1,21 +1,13 @@ -use std::{ - collections::HashMap, - hash::Hash, - fs::read_to_string, - sync::LazyLock, -}; - -use crossterm::{ - event::{KeyCode, KeyEvent, KeyModifiers} -}; +use std::{collections::HashMap, fs::read_to_string, hash::Hash, sync::LazyLock}; + +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use serde::{Deserialize, Serialize}; -use crate::toml::{TomlFileError, get_config_file_path}; +use crate::toml::{get_config_file_path, TomlFileError}; static DEFAULT_ACTIONS_STR: &str = include_str!("../../assets/actions.kv"); -static DEFAULT_ACTIONS: LazyLock> = LazyLock::new(|| { - Actions::from_str(DEFAULT_ACTIONS_STR).actions -}); +static DEFAULT_ACTIONS: LazyLock> = + LazyLock::new(|| Actions::from_str(DEFAULT_ACTIONS_STR).actions); #[derive(Eq, PartialEq, Copy, Clone, Debug, Serialize, Deserialize, Hash)] pub struct Shortcut { @@ -31,7 +23,10 @@ impl Shortcut { impl From for Shortcut { fn from(key: KeyEvent) -> Self { - Self { code: key.code, modifiers: key.modifiers } + Self { + code: key.code, + modifiers: key.modifiers, + } } } @@ -41,8 +36,8 @@ pub enum Action { Error, Quit, QueueNext, - ScreenAction(ScreenAction), - PlayerAction(PlayerAction), + Screen(ScreenAction), + Player(PlayerAction), } #[derive(Eq, PartialEq, Copy, Clone, Debug, Hash)] @@ -78,9 +73,9 @@ impl TryFrom<&str> for Action { }; if parent == "Player" { - PlayerAction::try_from(child).map(|v| Action::PlayerAction(v)) + PlayerAction::try_from(child).map(Action::Player) } else if parent == "Screen" { - ScreenAction::try_from(child).map(|v| Action::ScreenAction(v)) + ScreenAction::try_from(child).map(Action::Screen) } else { Err(()) } @@ -200,15 +195,11 @@ impl Actions { log::trace!("actions '{:#?}'", actions); - Self { - actions, - } + Self { actions } } #[allow(dead_code)] - pub fn to_file(&self) { - - } + pub fn to_file(&self) {} pub fn from_file() -> Result { let path = get_config_file_path("shortcuts")?; @@ -227,7 +218,6 @@ pub trait OnAction { fn on_action(&self, action: Action); } - pub trait OnActionMut { fn on_action(&mut self, action: Action); } diff --git a/src/structs/jolt.rs b/src/structs/jolt.rs index 88a2bc9..55afdce 100644 --- a/src/structs/jolt.rs +++ b/src/structs/jolt.rs @@ -1,11 +1,8 @@ -use std::{ - fs::read_to_string, - path::PathBuf, -}; +use std::{fs::read_to_string, path::PathBuf}; use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub struct Jolt { #[serde(skip)] pub path: PathBuf, @@ -19,10 +16,7 @@ impl Jolt { let jolt = read_to_string(path.as_path())?; let jolt: Jolt = toml::from_str(&jolt)?; - Ok(Jolt { - path, - ..jolt - }) + Ok(Jolt { path, ..jolt }) } } diff --git a/src/structs/song.rs b/src/structs/song.rs index 77ebd5e..dfcd360 100644 --- a/src/structs/song.rs +++ b/src/structs/song.rs @@ -1,21 +1,21 @@ use std::{ - path::PathBuf, - time::Duration, cmp::Ordering, + path::{Path, PathBuf}, + time::Duration, }; use lofty::{ + error::LoftyError, file::{AudioFile, TaggedFileExt}, probe::Probe, tag::Accessor, - error::LoftyError, }; use serde::{Deserialize, Serialize}; use crate::{ - structs::Jolt, + components::{directory_to_songs_and_folders, FileBrowserSelection}, cue::CueSheet, - components::{FileBrowserSelection, directory_to_songs_and_folders}, + structs::Jolt, }; #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] @@ -61,7 +61,7 @@ impl Song { }) } - pub fn from_dir(path: &PathBuf) -> Vec { + pub fn from_dir(path: &Path) -> Vec { // TODO: improve this. stop using the FileBrowser stuff. // check for songs, cue let entries = directory_to_songs_and_folders(path); @@ -73,24 +73,27 @@ impl Song { log::trace!(target: "::Song::from_dir", "{:#?}", jolt); - entries.iter().filter_map(|s| { - if let FileBrowserSelection::Song(song) = s { - let mut song = song.clone(); + entries + .iter() + .filter_map(|s| { + if let FileBrowserSelection::Song(song) = s { + let mut song = song.clone(); - if let Some(jolt) = jolt { - if jolt.album.is_some() { - song.album = jolt.album.clone(); - } - if jolt.artist.is_some() { - song.artist = jolt.artist.clone(); + if let Some(jolt) = jolt { + if jolt.album.is_some() { + song.album = jolt.album.clone(); + } + if jolt.artist.is_some() { + song.artist = jolt.artist.clone(); + } } - } - Some(song) - } else { - None - } - }).collect() + Some(song) + } else { + None + } + }) + .collect() } pub fn from_cue_sheet(cue_sheet: CueSheet) -> Vec { @@ -120,13 +123,17 @@ impl Song { .map(|t| Song { path: song_path.clone(), length: Duration::ZERO, - artist: jolt.as_ref().and_then(|j| j.artist.clone()).or(performer.clone()).or(t.performer()), + artist: jolt + .as_ref() + .and_then(|j| j.artist.clone()) + .or(performer.clone()) + .or(t.performer()), title: t.title(), start_time: t.start_time(), album: jolt.as_ref().and_then(|j| j.album.clone()).or(cue_sheet.title()), - track: t.index().split_whitespace().nth(0).map(|i| i.parse().ok()).flatten(), + track: t.index().split_whitespace().nth(0).and_then(|i| i.parse().ok()), year: song.year, // TODO: cue sheet year as a fallback? (it's usually stored as a comment in it...) - disc_number: jolt.as_ref().and_then(|j| j.disc_number.clone()), // There seems to be no standard disc number field for Cue Sheets... + disc_number: jolt.as_ref().and_then(|j| j.disc_number), // There seems to be no standard disc number field for Cue Sheets... }) .collect(); @@ -164,36 +171,30 @@ impl Song { impl Ord for Song { fn cmp(&self, other: &Self) -> Ordering { match (&self.album, &other.album) { - (Some(album_a), Some(album_b)) if album_a == album_b => { - match self.disc_number.cmp(&other.disc_number) { - Ordering::Equal => { - match (&self.track, &other.track) { - (Some(a), Some(b)) => a.cmp(b), - (Some(_), None) => Ordering::Greater, - (None, Some(_)) => Ordering::Less, - _ => self.title.cmp(&other.title), - } - } - o => o - } - }, - (Some(album_a), Some(album_b)) if album_a != album_b => { - match (self.year, other.year) { - (Some(ref year_a), Some(ref year_b)) => { - if year_a != year_b { - year_a.cmp(year_b) - } else { - album_a.cmp(album_b) - } - }, + (Some(album_a), Some(album_b)) if album_a == album_b => match self.disc_number.cmp(&other.disc_number) { + Ordering::Equal => match (&self.track, &other.track) { + (Some(a), Some(b)) => a.cmp(b), (Some(_), None) => Ordering::Greater, (None, Some(_)) => Ordering::Less, - _ => album_a.cmp(album_b) + _ => self.title.cmp(&other.title), + }, + o => o, + }, + (Some(album_a), Some(album_b)) if album_a != album_b => match (self.year, other.year) { + (Some(ref year_a), Some(ref year_b)) => { + if year_a != year_b { + year_a.cmp(year_b) + } else { + album_a.cmp(album_b) + } } + (Some(_), None) => Ordering::Greater, + (None, Some(_)) => Ordering::Less, + _ => album_a.cmp(album_b), }, (Some(_), None) => Ordering::Greater, (None, Some(_)) => Ordering::Less, - _ => self.title.cmp(&other.title) + _ => self.title.cmp(&other.title), } } } diff --git a/src/ui/currently_playing.rs b/src/ui/currently_playing.rs index f1fb611..d20f1ea 100644 --- a/src/ui/currently_playing.rs +++ b/src/ui/currently_playing.rs @@ -4,9 +4,9 @@ use log::error; use ratatui::{ layout::{Constraint, Layout}, prelude::*, - style::{Style}, - widgets::{Block, Borders, Gauge}, + style::Style, text::Line, + widgets::{Block, Borders, Gauge}, }; use crate::{ @@ -78,7 +78,7 @@ impl Widget for CurrentlyPlaying { if let Some(ref current_song) = self.current_song { let playing_file = Block::default() .style(Style::default().fg(self.theme.foreground)) - .title(song_to_string(¤t_song)) + .title(song_to_string(current_song)) .borders(Borders::NONE) .title_alignment(Alignment::Center) .title_position(ratatui::widgets::block::Position::Bottom); @@ -109,16 +109,12 @@ impl Widget for CurrentlyPlaying { (Some(playing_song_label), Some(queue_label)) => { format!("{playing_song_label} | {queue_label}") } - (None, Some(queue_label)) => { - format!("{queue_label}") - } - (Some(playing_song_label), None) => { - format!("{playing_song_label}") - } + (None, Some(queue_label)) => queue_label.to_string(), + (Some(playing_song_label), None) => playing_song_label.to_string(), _ => "".to_string(), }; - if playing_gauge_label.len() > 0 { + if !playing_gauge_label.is_empty() { let song_progress = match self.current_song { Some(ref song) => match song.length.as_secs_f64() { 0.0 => { @@ -139,7 +135,8 @@ impl Widget for CurrentlyPlaying { playing_gauge.render(area_bottom, buf); } - let [_, area_bottom_right] = Layout::horizontal([Constraint::Fill(1), Constraint::Length(6)]).areas(area_bottom); + let [_, area_bottom_right] = + Layout::horizontal([Constraint::Fill(1), Constraint::Length(6)]).areas(area_bottom); if self.is_paused { Line::from("PAUSED") diff --git a/src/ui/keyboard_handler.rs b/src/ui/keyboard_handler.rs index 64b94a3..f2b82dd 100644 --- a/src/ui/keyboard_handler.rs +++ b/src/ui/keyboard_handler.rs @@ -26,7 +26,7 @@ pub enum Component<'a> { } impl<'a> KeyboardHandlerRef<'a> for Component<'a> { - fn on_key(&self, key: KeyEvent) -> () { + fn on_key(&self, key: KeyEvent) { match self { Component::RefRc(ref target) => { target.on_key(key); diff --git a/src/ui/top_bar.rs b/src/ui/top_bar.rs index dbaeadf..ab6d414 100644 --- a/src/ui/top_bar.rs +++ b/src/ui/top_bar.rs @@ -2,7 +2,7 @@ use chrono::prelude::*; use ratatui::{ prelude::*, style::{Modifier, Style}, - text::{Span, Line}, + text::{Line, Span}, widgets::{Block, Tabs}, }; @@ -43,7 +43,8 @@ impl<'a> TopBar<'a> { impl<'a> Widget for TopBar<'a> { fn render(self, area: Rect, buf: &mut Buffer) { - let tab_titles: Vec = self.tab_titles + let tab_titles: Vec = self + .tab_titles .iter() .map(|t| { Line::from(Span::styled(