mirror of
				https://github.com/Dummi26/musicdb.git
				synced 2025-10-29 19:19:20 +01:00 
			
		
		
		
	add ways to modify tags, and add a Fav button to client
This commit is contained in:
		
							parent
							
								
									daad5c6aae
								
							
						
					
					
						commit
						b848a0d511
					
				| @ -234,7 +234,7 @@ pub fn main( | |||||||
|                 ), |                 ), | ||||||
|                 ( |                 ( | ||||||
|                     "Year".to_owned(), |                     "Year".to_owned(), | ||||||
|                     crate::gui_library::FilterType::TagWithValueInt("Year".to_owned(), 1990, 2000), |                     crate::gui_library::FilterType::TagWithValueInt("Year=".to_owned(), 1990, 2000), | ||||||
|                 ), |                 ), | ||||||
|             ], |             ], | ||||||
|             filter_presets_album: vec![ |             filter_presets_album: vec![ | ||||||
| @ -244,7 +244,7 @@ pub fn main( | |||||||
|                 ), |                 ), | ||||||
|                 ( |                 ( | ||||||
|                     "Year".to_owned(), |                     "Year".to_owned(), | ||||||
|                     crate::gui_library::FilterType::TagWithValueInt("Year".to_owned(), 1990, 2000), |                     crate::gui_library::FilterType::TagWithValueInt("Year=".to_owned(), 1990, 2000), | ||||||
|                 ), |                 ), | ||||||
|             ], |             ], | ||||||
|             filter_presets_artist: vec![ |             filter_presets_artist: vec![ | ||||||
| @ -254,7 +254,7 @@ pub fn main( | |||||||
|                 ), |                 ), | ||||||
|                 ( |                 ( | ||||||
|                     "Year".to_owned(), |                     "Year".to_owned(), | ||||||
|                     crate::gui_library::FilterType::TagWithValueInt("Year".to_owned(), 1990, 2000), |                     crate::gui_library::FilterType::TagWithValueInt("Year=".to_owned(), 1990, 2000), | ||||||
|                 ), |                 ), | ||||||
|             ], |             ], | ||||||
|             #[cfg(feature = "merscfg")] |             #[cfg(feature = "merscfg")] | ||||||
| @ -371,6 +371,18 @@ impl Gui { | |||||||
|                 | Command::RemoveSong(_) |                 | Command::RemoveSong(_) | ||||||
|                 | Command::RemoveAlbum(_) |                 | Command::RemoveAlbum(_) | ||||||
|                 | Command::RemoveArtist(_) |                 | Command::RemoveArtist(_) | ||||||
|  |                 | Command::TagSongFlagSet(..) | ||||||
|  |                 | Command::TagSongFlagUnset(..) | ||||||
|  |                 | Command::TagAlbumFlagSet(..) | ||||||
|  |                 | Command::TagAlbumFlagUnset(..) | ||||||
|  |                 | Command::TagArtistFlagSet(..) | ||||||
|  |                 | Command::TagArtistFlagUnset(..) | ||||||
|  |                 | Command::TagSongPropertySet(..) | ||||||
|  |                 | Command::TagSongPropertyUnset(..) | ||||||
|  |                 | Command::TagAlbumPropertySet(..) | ||||||
|  |                 | Command::TagAlbumPropertyUnset(..) | ||||||
|  |                 | Command::TagArtistPropertySet(..) | ||||||
|  |                 | Command::TagArtistPropertyUnset(..) | ||||||
|                 | Command::SetSongDuration(..) => { |                 | Command::SetSongDuration(..) => { | ||||||
|                     if let Some(s) = &*event_sender_arc.lock().unwrap() { |                     if let Some(s) = &*event_sender_arc.lock().unwrap() { | ||||||
|                         _ = s.send_event(GuiEvent::UpdatedLibrary); |                         _ = s.send_event(GuiEvent::UpdatedLibrary); | ||||||
|  | |||||||
| @ -1,4 +1,7 @@ | |||||||
| use std::{sync::Arc, time::Instant}; | use std::{ | ||||||
|  |     sync::{atomic::AtomicBool, Arc}, | ||||||
|  |     time::Instant, | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| use musicdb_lib::data::ArtistId; | use musicdb_lib::data::ArtistId; | ||||||
| use speedy2d::{color::Color, dimen::Vec2, image::ImageHandle, shape::Rectangle}; | use speedy2d::{color::Color, dimen::Vec2, image::ImageHandle, shape::Rectangle}; | ||||||
| @ -39,11 +42,14 @@ pub struct IdleDisplay { | |||||||
|     pub artist_image_to_cover_margin: f32, |     pub artist_image_to_cover_margin: f32, | ||||||
| 
 | 
 | ||||||
|     pub force_reset_texts: bool, |     pub force_reset_texts: bool, | ||||||
|  | 
 | ||||||
|  |     is_fav: (bool, Arc<AtomicBool>), | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl IdleDisplay { | impl IdleDisplay { | ||||||
|     pub fn new(config: GuiElemCfg) -> Self { |     pub fn new(config: GuiElemCfg) -> Self { | ||||||
|         let cover_bottom = 0.79; |         let cover_bottom = 0.79; | ||||||
|  |         let is_fav = Arc::new(AtomicBool::new(false)); | ||||||
|         Self { |         Self { | ||||||
|             config, |             config, | ||||||
|             idle_mode: 0.0, |             idle_mode: 0.0, | ||||||
| @ -67,7 +73,8 @@ impl IdleDisplay { | |||||||
|             ), |             ), | ||||||
|             c_side1_label: AdvancedLabel::new(GuiElemCfg::default(), Vec2::new(0.0, 0.5), vec![]), |             c_side1_label: AdvancedLabel::new(GuiElemCfg::default(), Vec2::new(0.0, 0.5), vec![]), | ||||||
|             c_side2_label: AdvancedLabel::new(GuiElemCfg::default(), Vec2::new(0.0, 0.5), vec![]), |             c_side2_label: AdvancedLabel::new(GuiElemCfg::default(), Vec2::new(0.0, 0.5), vec![]), | ||||||
|             c_buttons: PlayPause::new(GuiElemCfg::default()), |             is_fav: (false, Arc::clone(&is_fav)), | ||||||
|  |             c_buttons: PlayPause::new(GuiElemCfg::default(), is_fav), | ||||||
|             c_buttons_custom_pos: false, |             c_buttons_custom_pos: false, | ||||||
|             cover_aspect_ratio: AnimationController::new( |             cover_aspect_ratio: AnimationController::new( | ||||||
|                 1.0, |                 1.0, | ||||||
| @ -122,6 +129,19 @@ impl GuiElem for IdleDisplay { | |||||||
|         self.current_info.update(info, g); |         self.current_info.update(info, g); | ||||||
|         if self.current_info.new_song || self.force_reset_texts { |         if self.current_info.new_song || self.force_reset_texts { | ||||||
|             self.current_info.new_song = false; |             self.current_info.new_song = false; | ||||||
|  |             self.force_reset_texts = false; | ||||||
|  |             let is_fav = self | ||||||
|  |                 .current_info | ||||||
|  |                 .current_song | ||||||
|  |                 .and_then(|id| info.database.get_song(&id)) | ||||||
|  |                 .map(|song| song.general.tags.iter().any(|v| v == "Fav")) | ||||||
|  |                 .unwrap_or(false); | ||||||
|  |             if self.is_fav.0 != is_fav { | ||||||
|  |                 self.is_fav.0 = is_fav; | ||||||
|  |                 self.is_fav | ||||||
|  |                     .1 | ||||||
|  |                     .store(is_fav, std::sync::atomic::Ordering::Relaxed); | ||||||
|  |             } | ||||||
|             self.c_top_label.content = if let Some(song) = self.current_info.current_song { |             self.c_top_label.content = if let Some(song) = self.current_info.current_song { | ||||||
|                 info.gui_config |                 info.gui_config | ||||||
|                     .idle_top_text |                     .idle_top_text | ||||||
| @ -278,9 +298,10 @@ impl GuiElem for IdleDisplay { | |||||||
|             self.c_side2_label.config_mut().pos = |             self.c_side2_label.config_mut().pos = | ||||||
|                 Rectangle::from_tuples((left, ai_top), (max_right, bottom)); |                 Rectangle::from_tuples((left, ai_top), (max_right, bottom)); | ||||||
|             // limit width of c_buttons
 |             // limit width of c_buttons
 | ||||||
|             let buttons_right_pos = 0.99; |             let buttons_right_pos = 1.0; | ||||||
|             let buttons_width_max = info.pos.height() * 0.08 / 0.3 / info.pos.width(); |             let buttons_width_max = info.pos.height() * 0.08 * 4.0 / info.pos.width(); | ||||||
|             let buttons_width = buttons_width_max.min(0.2); |             // buttons use at most half the width (set to 0.2 later, when screen space is used for other things)
 | ||||||
|  |             let buttons_width = buttons_width_max.min(0.5); | ||||||
|             if !self.c_buttons_custom_pos { |             if !self.c_buttons_custom_pos { | ||||||
|                 self.c_buttons.config_mut().pos = Rectangle::from_tuples( |                 self.c_buttons.config_mut().pos = Rectangle::from_tuples( | ||||||
|                     (buttons_right_pos - buttons_width, 0.86), |                     (buttons_right_pos - buttons_width, 0.86), | ||||||
| @ -309,6 +330,7 @@ impl GuiElem for IdleDisplay { | |||||||
|     } |     } | ||||||
|     fn updated_library(&mut self) { |     fn updated_library(&mut self) { | ||||||
|         self.current_info.update = true; |         self.current_info.update = true; | ||||||
|  |         self.force_reset_texts = true; | ||||||
|     } |     } | ||||||
|     fn updated_queue(&mut self) { |     fn updated_queue(&mut self) { | ||||||
|         self.current_info.update = true; |         self.current_info.update = true; | ||||||
|  | |||||||
| @ -1,3 +1,5 @@ | |||||||
|  | use std::sync::{atomic::AtomicBool, Arc}; | ||||||
|  | 
 | ||||||
| use musicdb_lib::server::Command; | use musicdb_lib::server::Command; | ||||||
| use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, Graphics2D}; | use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, Graphics2D}; | ||||||
| 
 | 
 | ||||||
| @ -8,17 +10,44 @@ use crate::{ | |||||||
| 
 | 
 | ||||||
| pub struct PlayPause { | pub struct PlayPause { | ||||||
|     config: GuiElemCfg, |     config: GuiElemCfg, | ||||||
|  |     set_fav: Button<[FavIcon; 1]>, | ||||||
|     to_zero: Button<[Panel<()>; 1]>, |     to_zero: Button<[Panel<()>; 1]>, | ||||||
|     play_pause: Button<[PlayPauseDisplay; 1]>, |     play_pause: Button<[PlayPauseDisplay; 1]>, | ||||||
|     to_end: Button<[NextSongShape; 1]>, |     to_end: Button<[NextSongShape; 1]>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl PlayPause { | impl PlayPause { | ||||||
|     pub fn new(config: GuiElemCfg) -> Self { |     pub fn new(config: GuiElemCfg, is_fav: Arc<AtomicBool>) -> Self { | ||||||
|         Self { |         Self { | ||||||
|             config, |             config, | ||||||
|  |             set_fav: Button::new( | ||||||
|  |                 GuiElemCfg::at(Rectangle::from_tuples((0.01, 0.01), (0.24, 0.99))), | ||||||
|  |                 |_| { | ||||||
|  |                     vec![GuiAction::Build(Box::new(|db| { | ||||||
|  |                         if let Some(song_id) = db.queue.get_current_song() { | ||||||
|  |                             if let Some(song) = db.get_song(song_id) { | ||||||
|  |                                 vec![GuiAction::SendToServer( | ||||||
|  |                                     if song.general.tags.iter().any(|v| v == "Fav") { | ||||||
|  |                                         Command::TagSongFlagUnset(*song_id, "Fav".to_owned()) | ||||||
|  |                                     } else { | ||||||
|  |                                         Command::TagSongFlagSet(*song_id, "Fav".to_owned()) | ||||||
|  |                                     }, | ||||||
|  |                                 )] | ||||||
|  |                             } else { | ||||||
|  |                                 vec![] | ||||||
|  |                             } | ||||||
|  |                         } else { | ||||||
|  |                             vec![] | ||||||
|  |                         } | ||||||
|  |                     }))] | ||||||
|  |                 }, | ||||||
|  |                 [FavIcon::new( | ||||||
|  |                     GuiElemCfg::at(Rectangle::from_tuples((0.2, 0.2), (0.8, 0.8))), | ||||||
|  |                     is_fav, | ||||||
|  |                 )], | ||||||
|  |             ), | ||||||
|             to_zero: Button::new( |             to_zero: Button::new( | ||||||
|                 GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (0.3, 1.0))), |                 GuiElemCfg::at(Rectangle::from_tuples((0.26, 0.01), (0.49, 0.99))), | ||||||
|                 |_| vec![GuiAction::SendToServer(Command::Stop)], |                 |_| vec![GuiAction::SendToServer(Command::Stop)], | ||||||
|                 [Panel::with_background( |                 [Panel::with_background( | ||||||
|                     GuiElemCfg::at(Rectangle::from_tuples((0.2, 0.2), (0.8, 0.8))), |                     GuiElemCfg::at(Rectangle::from_tuples((0.2, 0.2), (0.8, 0.8))), | ||||||
| @ -27,7 +56,7 @@ impl PlayPause { | |||||||
|                 )], |                 )], | ||||||
|             ), |             ), | ||||||
|             play_pause: Button::new( |             play_pause: Button::new( | ||||||
|                 GuiElemCfg::at(Rectangle::from_tuples((0.35, 0.0), (0.65, 1.0))), |                 GuiElemCfg::at(Rectangle::from_tuples((0.51, 0.01), (0.74, 0.99))), | ||||||
|                 |btn| { |                 |btn| { | ||||||
|                     vec![GuiAction::SendToServer(if btn.children[0].is_playing { |                     vec![GuiAction::SendToServer(if btn.children[0].is_playing { | ||||||
|                         Command::Pause |                         Command::Pause | ||||||
| @ -40,7 +69,7 @@ impl PlayPause { | |||||||
|                 ))], |                 ))], | ||||||
|             ), |             ), | ||||||
|             to_end: Button::new( |             to_end: Button::new( | ||||||
|                 GuiElemCfg::at(Rectangle::from_tuples((0.7, 0.0), (1.0, 1.0))), |                 GuiElemCfg::at(Rectangle::from_tuples((0.76, 0.01), (0.99, 0.99))), | ||||||
|                 |_| vec![GuiAction::SendToServer(Command::NextSong)], |                 |_| vec![GuiAction::SendToServer(Command::NextSong)], | ||||||
|                 [NextSongShape::new(GuiElemCfg::at(Rectangle::from_tuples( |                 [NextSongShape::new(GuiElemCfg::at(Rectangle::from_tuples( | ||||||
|                     (0.2, 0.2), |                     (0.2, 0.2), | ||||||
| @ -175,6 +204,90 @@ impl GuiElem for NextSongShape { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | struct FavIcon { | ||||||
|  |     config: GuiElemCfg, | ||||||
|  |     is_fav: Arc<AtomicBool>, | ||||||
|  | } | ||||||
|  | impl FavIcon { | ||||||
|  |     pub fn new(config: GuiElemCfg, is_fav: Arc<AtomicBool>) -> Self { | ||||||
|  |         Self { config, is_fav } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | impl GuiElem for FavIcon { | ||||||
|  |     fn draw(&mut self, info: &mut DrawInfo, g: &mut Graphics2D) { | ||||||
|  |         let clr = if self.is_fav.load(std::sync::atomic::Ordering::Relaxed) { | ||||||
|  |             Color::from_rgb(0.7, 0.1, 0.1) | ||||||
|  |         } else { | ||||||
|  |             Color::from_rgb(0.3, 0.2, 0.2) | ||||||
|  |         }; | ||||||
|  |         let pos = if info.pos.width() > info.pos.height() { | ||||||
|  |             let c = info.pos.top_left().x + info.pos.width() * 0.5; | ||||||
|  |             let d = info.pos.height() * 0.5; | ||||||
|  |             Rectangle::from_tuples( | ||||||
|  |                 (c - d, info.pos.top_left().y), | ||||||
|  |                 (c + d, info.pos.bottom_right().y), | ||||||
|  |             ) | ||||||
|  |         } else if info.pos.height() > info.pos.width() { | ||||||
|  |             let c = info.pos.top_left().y + info.pos.height() * 0.5; | ||||||
|  |             let d = info.pos.width() * 0.5; | ||||||
|  |             Rectangle::from_tuples( | ||||||
|  |                 (info.pos.top_left().x, c - d), | ||||||
|  |                 (info.pos.bottom_right().x, c + d), | ||||||
|  |             ) | ||||||
|  |         } else { | ||||||
|  |             info.pos.clone() | ||||||
|  |         }; | ||||||
|  |         let circle_radius = 0.25; | ||||||
|  |         let out_dist = pos.height() * circle_radius * std::f32::consts::SQRT_2 * 0.5; | ||||||
|  |         let x_cntr = pos.top_left().x + pos.width() * 0.5; | ||||||
|  |         let left_circle_cntr = Vec2::new( | ||||||
|  |             pos.top_left().x + pos.width() * circle_radius, | ||||||
|  |             pos.top_left().y + pos.height() * circle_radius, | ||||||
|  |         ); | ||||||
|  |         let right_circle_cntr = Vec2::new( | ||||||
|  |             pos.bottom_right().x - pos.width() * circle_radius, | ||||||
|  |             pos.top_left().y + pos.height() * circle_radius, | ||||||
|  |         ); | ||||||
|  |         let circle_radius = circle_radius * pos.height(); | ||||||
|  |         let x1 = x_cntr - circle_radius - out_dist; | ||||||
|  |         let x2 = x_cntr + circle_radius + out_dist; | ||||||
|  |         let h1 = pos.top_left().y + circle_radius; | ||||||
|  |         let h2 = pos.top_left().y + circle_radius + out_dist; | ||||||
|  |         g.draw_circle(left_circle_cntr, circle_radius, clr); | ||||||
|  |         g.draw_circle(right_circle_cntr, circle_radius, clr); | ||||||
|  |         g.draw_rectangle(Rectangle::from_tuples((x1, h1), (x2, h2)), clr); | ||||||
|  |         g.draw_triangle( | ||||||
|  |             [ | ||||||
|  |                 Vec2::new(x1, h2), | ||||||
|  |                 Vec2::new(x2, h2), | ||||||
|  |                 Vec2::new(x_cntr, pos.bottom_right().y), | ||||||
|  |             ], | ||||||
|  |             clr, | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |     fn config(&self) -> &GuiElemCfg { | ||||||
|  |         &self.config | ||||||
|  |     } | ||||||
|  |     fn config_mut(&mut self) -> &mut GuiElemCfg { | ||||||
|  |         &mut self.config | ||||||
|  |     } | ||||||
|  |     fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> { | ||||||
|  |         Box::new([].into_iter()) | ||||||
|  |     } | ||||||
|  |     fn any(&self) -> &dyn std::any::Any { | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  |     fn any_mut(&mut self) -> &mut dyn std::any::Any { | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  |     fn elem(&self) -> &dyn GuiElem { | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  |     fn elem_mut(&mut self) -> &mut dyn GuiElem { | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| impl GuiElem for PlayPause { | impl GuiElem for PlayPause { | ||||||
|     fn config(&self) -> &GuiElemCfg { |     fn config(&self) -> &GuiElemCfg { | ||||||
|         &self.config |         &self.config | ||||||
| @ -185,6 +298,7 @@ impl GuiElem for PlayPause { | |||||||
|     fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> { |     fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> { | ||||||
|         Box::new( |         Box::new( | ||||||
|             [ |             [ | ||||||
|  |                 self.set_fav.elem_mut(), | ||||||
|                 self.to_zero.elem_mut(), |                 self.to_zero.elem_mut(), | ||||||
|                 self.play_pause.elem_mut(), |                 self.play_pause.elem_mut(), | ||||||
|                 self.to_end.elem_mut(), |                 self.to_end.elem_mut(), | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | use musicdb_lib::server::Command; | ||||||
| use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, Graphics2D}; | use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, Graphics2D}; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
| @ -55,6 +56,7 @@ pub struct SettingsContent { | |||||||
|     pub line_height: Panel<(Label, Slider)>, |     pub line_height: Panel<(Label, Slider)>, | ||||||
|     pub scroll_sensitivity: Panel<(Label, Slider)>, |     pub scroll_sensitivity: Panel<(Label, Slider)>, | ||||||
|     pub idle_time: Panel<(Label, Slider)>, |     pub idle_time: Panel<(Label, Slider)>, | ||||||
|  |     pub save_button: Button<[Label; 1]>, | ||||||
| } | } | ||||||
| impl GuiElemChildren for SettingsContent { | impl GuiElemChildren for SettingsContent { | ||||||
|     fn iter(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> { |     fn iter(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> { | ||||||
| @ -259,6 +261,17 @@ impl SettingsContent { | |||||||
|                     ), |                     ), | ||||||
|                 ), |                 ), | ||||||
|             ), |             ), | ||||||
|  |             save_button: Button::new( | ||||||
|  |                 GuiElemCfg::default(), | ||||||
|  |                 |_| vec![GuiAction::SendToServer(Command::Save)], | ||||||
|  |                 [Label::new( | ||||||
|  |                     GuiElemCfg::default(), | ||||||
|  |                     "Server: Save Changes".to_string(), | ||||||
|  |                     Color::WHITE, | ||||||
|  |                     None, | ||||||
|  |                     Vec2::new(0.5, 0.5), | ||||||
|  |                 )], | ||||||
|  |             ), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,4 +1,7 @@ | |||||||
| use std::time::Instant; | use std::{ | ||||||
|  |     sync::{atomic::AtomicBool, Arc}, | ||||||
|  |     time::Instant, | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| use speedy2d::{dimen::Vec2, shape::Rectangle}; | use speedy2d::{dimen::Vec2, shape::Rectangle}; | ||||||
| 
 | 
 | ||||||
| @ -18,10 +21,12 @@ pub struct StatusBar { | |||||||
|     c_song_label: AdvancedLabel, |     c_song_label: AdvancedLabel, | ||||||
|     pub force_reset_texts: bool, |     pub force_reset_texts: bool, | ||||||
|     c_buttons: PlayPause, |     c_buttons: PlayPause, | ||||||
|  |     is_fav: (bool, Arc<AtomicBool>), | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl StatusBar { | impl StatusBar { | ||||||
|     pub fn new(config: GuiElemCfg) -> Self { |     pub fn new(config: GuiElemCfg) -> Self { | ||||||
|  |         let is_fav = Arc::new(AtomicBool::new(false)); | ||||||
|         Self { |         Self { | ||||||
|             config, |             config, | ||||||
|             idle_mode: 0.0, |             idle_mode: 0.0, | ||||||
| @ -37,7 +42,8 @@ impl StatusBar { | |||||||
|             ), |             ), | ||||||
|             c_song_label: AdvancedLabel::new(GuiElemCfg::default(), Vec2::new(0.0, 0.5), vec![]), |             c_song_label: AdvancedLabel::new(GuiElemCfg::default(), Vec2::new(0.0, 0.5), vec![]), | ||||||
|             force_reset_texts: false, |             force_reset_texts: false, | ||||||
|             c_buttons: PlayPause::new(GuiElemCfg::default()), |             is_fav: (false, Arc::clone(&is_fav)), | ||||||
|  |             c_buttons: PlayPause::new(GuiElemCfg::default(), is_fav), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -50,6 +56,20 @@ impl GuiElem for StatusBar { | |||||||
|         self.current_info.update(info, g); |         self.current_info.update(info, g); | ||||||
|         if self.current_info.new_song || self.force_reset_texts { |         if self.current_info.new_song || self.force_reset_texts { | ||||||
|             self.current_info.new_song = false; |             self.current_info.new_song = false; | ||||||
|  |             self.force_reset_texts = false; | ||||||
|  |             let is_fav = self | ||||||
|  |                 .current_info | ||||||
|  |                 .current_song | ||||||
|  |                 .and_then(|id| info.database.get_song(&id)) | ||||||
|  |                 .map(|song| song.general.tags.iter().any(|v| v == "Fav")) | ||||||
|  |                 .unwrap_or(false); | ||||||
|  |             eprintln!("is_fav: {is_fav}"); | ||||||
|  |             if self.is_fav.0 != is_fav { | ||||||
|  |                 self.is_fav.0 = is_fav; | ||||||
|  |                 self.is_fav | ||||||
|  |                     .1 | ||||||
|  |                     .store(is_fav, std::sync::atomic::Ordering::Relaxed); | ||||||
|  |             } | ||||||
|             self.c_song_label.content = if let Some(song) = self.current_info.current_song { |             self.c_song_label.content = if let Some(song) = self.current_info.current_song { | ||||||
|                 info.gui_config |                 info.gui_config | ||||||
|                     .status_bar_text |                     .status_bar_text | ||||||
| @ -78,7 +98,7 @@ impl GuiElem for StatusBar { | |||||||
|             } |             } | ||||||
|             // limit width of c_buttons
 |             // limit width of c_buttons
 | ||||||
|             let buttons_right_pos = 0.99; |             let buttons_right_pos = 0.99; | ||||||
|             let buttons_width_max = info.pos.height() * 0.7 / 0.3 / info.pos.width(); |             let buttons_width_max = info.pos.height() * 0.7 * 4.0 / info.pos.width(); | ||||||
|             let buttons_width = buttons_width_max.min(0.2); |             let buttons_width = buttons_width_max.min(0.2); | ||||||
|             self.c_buttons.config_mut().pos = Rectangle::from_tuples( |             self.c_buttons.config_mut().pos = Rectangle::from_tuples( | ||||||
|                 (buttons_right_pos - buttons_width, 0.15), |                 (buttons_right_pos - buttons_width, 0.15), | ||||||
| @ -130,6 +150,7 @@ impl GuiElem for StatusBar { | |||||||
|     } |     } | ||||||
|     fn updated_library(&mut self) { |     fn updated_library(&mut self) { | ||||||
|         self.current_info.update = true; |         self.current_info.update = true; | ||||||
|  |         self.force_reset_texts = true; | ||||||
|     } |     } | ||||||
|     fn updated_queue(&mut self) { |     fn updated_queue(&mut self) { | ||||||
|         self.current_info.update = true; |         self.current_info.update = true; | ||||||
|  | |||||||
| @ -359,6 +359,96 @@ impl Database { | |||||||
|             Command::RemoveArtist(artist) => { |             Command::RemoveArtist(artist) => { | ||||||
|                 _ = self.remove_artist(artist); |                 _ = self.remove_artist(artist); | ||||||
|             } |             } | ||||||
|  |             Command::TagSongFlagSet(id, tag) => { | ||||||
|  |                 if let Some(v) = self.get_song_mut(&id) { | ||||||
|  |                     if !v.general.tags.contains(&tag) { | ||||||
|  |                         v.general.tags.push(tag); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             Command::TagSongFlagUnset(id, tag) => { | ||||||
|  |                 if let Some(v) = self.get_song_mut(&id) { | ||||||
|  |                     if let Some(i) = v.general.tags.iter().position(|v| v == &tag) { | ||||||
|  |                         v.general.tags.remove(i); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             Command::TagAlbumFlagSet(id, tag) => { | ||||||
|  |                 if let Some(v) = self.albums.get_mut(&id) { | ||||||
|  |                     if !v.general.tags.contains(&tag) { | ||||||
|  |                         v.general.tags.push(tag); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             Command::TagAlbumFlagUnset(id, tag) => { | ||||||
|  |                 if let Some(v) = self.albums.get_mut(&id) { | ||||||
|  |                     if let Some(i) = v.general.tags.iter().position(|v| v == &tag) { | ||||||
|  |                         v.general.tags.remove(i); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             Command::TagArtistFlagSet(id, tag) => { | ||||||
|  |                 if let Some(v) = self.artists.get_mut(&id) { | ||||||
|  |                     if !v.general.tags.contains(&tag) { | ||||||
|  |                         v.general.tags.push(tag); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             Command::TagArtistFlagUnset(id, tag) => { | ||||||
|  |                 if let Some(v) = self.artists.get_mut(&id) { | ||||||
|  |                     if let Some(i) = v.general.tags.iter().position(|v| v == &tag) { | ||||||
|  |                         v.general.tags.remove(i); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             Command::TagSongPropertySet(id, key, val) => { | ||||||
|  |                 if let Some(v) = self.get_song_mut(&id) { | ||||||
|  |                     let new = format!("{key}{val}"); | ||||||
|  |                     if let Some(v) = v.general.tags.iter_mut().find(|v| v.starts_with(&key)) { | ||||||
|  |                         *v = new; | ||||||
|  |                     } else { | ||||||
|  |                         v.general.tags.push(new); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             Command::TagSongPropertyUnset(id, key) => { | ||||||
|  |                 if let Some(v) = self.get_song_mut(&id) { | ||||||
|  |                     let tags = std::mem::replace(&mut v.general.tags, vec![]); | ||||||
|  |                     v.general.tags = tags.into_iter().filter(|v| !v.starts_with(&key)).collect(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             Command::TagAlbumPropertySet(id, key, val) => { | ||||||
|  |                 if let Some(v) = self.albums.get_mut(&id) { | ||||||
|  |                     let new = format!("{key}{val}"); | ||||||
|  |                     if let Some(v) = v.general.tags.iter_mut().find(|v| v.starts_with(&key)) { | ||||||
|  |                         *v = new; | ||||||
|  |                     } else { | ||||||
|  |                         v.general.tags.push(new); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             Command::TagAlbumPropertyUnset(id, key) => { | ||||||
|  |                 if let Some(v) = self.albums.get_mut(&id) { | ||||||
|  |                     let tags = std::mem::replace(&mut v.general.tags, vec![]); | ||||||
|  |                     v.general.tags = tags.into_iter().filter(|v| !v.starts_with(&key)).collect(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             Command::TagArtistPropertySet(id, key, val) => { | ||||||
|  |                 if let Some(v) = self.artists.get_mut(&id) { | ||||||
|  |                     let new = format!("{key}{val}"); | ||||||
|  |                     if let Some(v) = v.general.tags.iter_mut().find(|v| v.starts_with(&key)) { | ||||||
|  |                         *v = new; | ||||||
|  |                     } else { | ||||||
|  |                         v.general.tags.push(new); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             Command::TagArtistPropertyUnset(id, key) => { | ||||||
|  |                 if let Some(v) = self.artists.get_mut(&id) { | ||||||
|  |                     let tags = std::mem::replace(&mut v.general.tags, vec![]); | ||||||
|  |                     v.general.tags = tags.into_iter().filter(|v| !v.starts_with(&key)).collect(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|             Command::SetSongDuration(id, duration) => { |             Command::SetSongDuration(id, duration) => { | ||||||
|                 if let Some(song) = self.get_song_mut(&id) { |                 if let Some(song) = self.get_song_mut(&id) { | ||||||
|                     song.duration_millis = duration; |                     song.duration_millis = duration; | ||||||
|  | |||||||
| @ -31,7 +31,6 @@ pub enum Command { | |||||||
|     Resume, |     Resume, | ||||||
|     Pause, |     Pause, | ||||||
|     Stop, |     Stop, | ||||||
|     Save, |  | ||||||
|     NextSong, |     NextSong, | ||||||
|     SyncDatabase(Vec<Artist>, Vec<Album>, Vec<Song>), |     SyncDatabase(Vec<Artist>, Vec<Album>, Vec<Song>), | ||||||
|     QueueUpdate(Vec<usize>, Queue), |     QueueUpdate(Vec<usize>, Queue), | ||||||
| @ -40,6 +39,7 @@ pub enum Command { | |||||||
|     QueueRemove(Vec<usize>), |     QueueRemove(Vec<usize>), | ||||||
|     QueueGoto(Vec<usize>), |     QueueGoto(Vec<usize>), | ||||||
|     QueueSetShuffle(Vec<usize>, Vec<usize>), |     QueueSetShuffle(Vec<usize>, Vec<usize>), | ||||||
|  | 
 | ||||||
|     /// .id field is ignored!
 |     /// .id field is ignored!
 | ||||||
|     AddSong(Song), |     AddSong(Song), | ||||||
|     /// .id field is ignored!
 |     /// .id field is ignored!
 | ||||||
| @ -54,7 +54,26 @@ pub enum Command { | |||||||
|     RemoveArtist(ArtistId), |     RemoveArtist(ArtistId), | ||||||
|     ModifyArtist(Artist), |     ModifyArtist(Artist), | ||||||
|     SetSongDuration(SongId, u64), |     SetSongDuration(SongId, u64), | ||||||
|  |     /// Add the given Tag to the song's tags, if it isn't set already.
 | ||||||
|  |     TagSongFlagSet(SongId, String), | ||||||
|  |     /// Remove the given Tag fron the song's tags, if it exists.
 | ||||||
|  |     TagSongFlagUnset(SongId, String), | ||||||
|  |     TagAlbumFlagSet(AlbumId, String), | ||||||
|  |     TagAlbumFlagUnset(AlbumId, String), | ||||||
|  |     TagArtistFlagSet(ArtistId, String), | ||||||
|  |     TagArtistFlagUnset(ArtistId, String), | ||||||
|  |     /// For the arguments `Key`, `Val`: If the song has a Tag `Key<anything>`, it will be removed. Then, `KeyVal` will be added.
 | ||||||
|  |     /// For example, to set "Year=2010", Key would be "Year=", and Val would be "2010". Then, "Year=1990", ..., would be removed and "Year=2010" would be added.
 | ||||||
|  |     TagSongPropertySet(SongId, String, String), | ||||||
|  |     /// For the arguments `Key`, `Val`: If the song has a Tag `Key<anything>`, it will be removed.
 | ||||||
|  |     TagSongPropertyUnset(SongId, String), | ||||||
|  |     TagAlbumPropertySet(AlbumId, String, String), | ||||||
|  |     TagAlbumPropertyUnset(AlbumId, String), | ||||||
|  |     TagArtistPropertySet(ArtistId, String, String), | ||||||
|  |     TagArtistPropertyUnset(ArtistId, String), | ||||||
|  | 
 | ||||||
|     InitComplete, |     InitComplete, | ||||||
|  |     Save, | ||||||
|     ErrorInfo(String, String), |     ErrorInfo(String, String), | ||||||
| } | } | ||||||
| impl Command { | impl Command { | ||||||
| @ -195,102 +214,208 @@ pub fn handle_one_connection_as_control( | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | const BYTE_RESUME: u8 = 0b11000000; | ||||||
|  | const BYTE_PAUSE: u8 = 0b00110000; | ||||||
|  | const BYTE_STOP: u8 = 0b11110000; | ||||||
|  | const BYTE_NEXT_SONG: u8 = 0b11110010; | ||||||
|  | 
 | ||||||
|  | const BYTE_SYNC_DATABASE: u8 = 0b01011000; | ||||||
|  | const BYTE_QUEUE_UPDATE: u8 = 0b00011100; | ||||||
|  | const BYTE_QUEUE_ADD: u8 = 0b00011010; | ||||||
|  | const BYTE_QUEUE_INSERT: u8 = 0b00011110; | ||||||
|  | const BYTE_QUEUE_REMOVE: u8 = 0b00011001; | ||||||
|  | const BYTE_QUEUE_GOTO: u8 = 0b00011011; | ||||||
|  | const BYTE_QUEUE_SET_SHUFFLE: u8 = 0b10011011; | ||||||
|  | 
 | ||||||
|  | const BYTE_ADD_SONG: u8 = 0b01010000; | ||||||
|  | const BYTE_ADD_ALBUM: u8 = 0b01010011; | ||||||
|  | const BYTE_ADD_ARTIST: u8 = 0b01011100; | ||||||
|  | const BYTE_ADD_COVER: u8 = 0b01011101; | ||||||
|  | const BYTE_MODIFY_SONG: u8 = 0b10010000; | ||||||
|  | const BYTE_MODIFY_ALBUM: u8 = 0b10010011; | ||||||
|  | const BYTE_MODIFY_ARTIST: u8 = 0b10011100; | ||||||
|  | const BYTE_REMOVE_SONG: u8 = 0b11010000; | ||||||
|  | const BYTE_REMOVE_ALBUM: u8 = 0b11010011; | ||||||
|  | const BYTE_REMOVE_ARTIST: u8 = 0b11011100; | ||||||
|  | const BYTE_TAG_SONG_FLAG_SET: u8 = 0b11100000; | ||||||
|  | const BYTE_TAG_SONG_FLAG_UNSET: u8 = 0b11100001; | ||||||
|  | const BYTE_TAG_ALBUM_FLAG_SET: u8 = 0b11100010; | ||||||
|  | const BYTE_TAG_ALBUM_FLAG_UNSET: u8 = 0b11100011; | ||||||
|  | const BYTE_TAG_ARTIST_FLAG_SET: u8 =   0b11100110; | ||||||
|  | const BYTE_TAG_ARTIST_FLAG_UNSET: u8 = 0b11100111; | ||||||
|  | const BYTE_TAG_SONG_PROPERTY_SET: u8 = 0b11101001; | ||||||
|  | const BYTE_TAG_SONG_PROPERTY_UNSET: u8 = 0b11101010; | ||||||
|  | const BYTE_TAG_ALBUM_PROPERTY_SET: u8 = 0b11101011; | ||||||
|  | const BYTE_TAG_ALBUM_PROPERTY_UNSET: u8 = 0b11101100; | ||||||
|  | const BYTE_TAG_ARTIST_PROPERTY_SET: u8 = 0b11101110; | ||||||
|  | const BYTE_TAG_ARTIST_PROPERTY_UNSET: u8 = 0b11101111; | ||||||
|  | 
 | ||||||
|  | const BYTE_SET_SONG_DURATION: u8 = 0b11111000; | ||||||
|  | 
 | ||||||
|  | const BYTE_INIT_COMPLETE: u8 = 0b00110001; | ||||||
|  | const BYTE_SAVE: u8 = 0b11110011; | ||||||
|  | const BYTE_ERRORINFO: u8 = 0b11011011; | ||||||
|  | 
 | ||||||
| impl ToFromBytes for Command { | impl ToFromBytes for Command { | ||||||
|     fn to_bytes<T>(&self, s: &mut T) -> Result<(), std::io::Error> |     fn to_bytes<T>(&self, s: &mut T) -> Result<(), std::io::Error> | ||||||
|     where |     where | ||||||
|         T: Write, |         T: Write, | ||||||
|     { |     { | ||||||
|         match self { |         match self { | ||||||
|             Self::Resume => s.write_all(&[0b11000000])?, |             Self::Resume => s.write_all(&[BYTE_RESUME])?, | ||||||
|             Self::Pause => s.write_all(&[0b00110000])?, |             Self::Pause => s.write_all(&[BYTE_PAUSE])?, | ||||||
|             Self::Stop => s.write_all(&[0b11110000])?, |             Self::Stop => s.write_all(&[BYTE_STOP])?, | ||||||
|             Self::Save => s.write_all(&[0b11110011])?, |             Self::NextSong => s.write_all(&[BYTE_NEXT_SONG])?, | ||||||
|             Self::NextSong => s.write_all(&[0b11110010])?, |  | ||||||
|             Self::SyncDatabase(a, b, c) => { |             Self::SyncDatabase(a, b, c) => { | ||||||
|                 s.write_all(&[0b01011000])?; |                 s.write_all(&[BYTE_SYNC_DATABASE])?; | ||||||
|                 a.to_bytes(s)?; |                 a.to_bytes(s)?; | ||||||
|                 b.to_bytes(s)?; |                 b.to_bytes(s)?; | ||||||
|                 c.to_bytes(s)?; |                 c.to_bytes(s)?; | ||||||
|             } |             } | ||||||
|             Self::QueueUpdate(index, new_data) => { |             Self::QueueUpdate(index, new_data) => { | ||||||
|                 s.write_all(&[0b00011100])?; |                 s.write_all(&[BYTE_QUEUE_UPDATE])?; | ||||||
|                 index.to_bytes(s)?; |                 index.to_bytes(s)?; | ||||||
|                 new_data.to_bytes(s)?; |                 new_data.to_bytes(s)?; | ||||||
|             } |             } | ||||||
|             Self::QueueAdd(index, new_data) => { |             Self::QueueAdd(index, new_data) => { | ||||||
|                 s.write_all(&[0b00011010])?; |                 s.write_all(&[BYTE_QUEUE_ADD])?; | ||||||
|                 index.to_bytes(s)?; |                 index.to_bytes(s)?; | ||||||
|                 new_data.to_bytes(s)?; |                 new_data.to_bytes(s)?; | ||||||
|             } |             } | ||||||
|             Self::QueueInsert(index, pos, new_data) => { |             Self::QueueInsert(index, pos, new_data) => { | ||||||
|                 s.write_all(&[0b00011110])?; |                 s.write_all(&[BYTE_QUEUE_INSERT])?; | ||||||
|                 index.to_bytes(s)?; |                 index.to_bytes(s)?; | ||||||
|                 pos.to_bytes(s)?; |                 pos.to_bytes(s)?; | ||||||
|                 new_data.to_bytes(s)?; |                 new_data.to_bytes(s)?; | ||||||
|             } |             } | ||||||
|             Self::QueueRemove(index) => { |             Self::QueueRemove(index) => { | ||||||
|                 s.write_all(&[0b00011001])?; |                 s.write_all(&[BYTE_QUEUE_REMOVE])?; | ||||||
|                 index.to_bytes(s)?; |                 index.to_bytes(s)?; | ||||||
|             } |             } | ||||||
|             Self::QueueGoto(index) => { |             Self::QueueGoto(index) => { | ||||||
|                 s.write_all(&[0b00011011])?; |                 s.write_all(&[BYTE_QUEUE_GOTO])?; | ||||||
|                 index.to_bytes(s)?; |                 index.to_bytes(s)?; | ||||||
|             } |             } | ||||||
|             Self::QueueSetShuffle(path, map) => { |             Self::QueueSetShuffle(path, map) => { | ||||||
|                 s.write_all(&[0b10011011])?; |                 s.write_all(&[BYTE_QUEUE_SET_SHUFFLE])?; | ||||||
|                 path.to_bytes(s)?; |                 path.to_bytes(s)?; | ||||||
|                 map.to_bytes(s)?; |                 map.to_bytes(s)?; | ||||||
|             } |             } | ||||||
|             Self::AddSong(song) => { |             Self::AddSong(song) => { | ||||||
|                 s.write_all(&[0b01010000])?; |                 s.write_all(&[BYTE_ADD_SONG])?; | ||||||
|                 song.to_bytes(s)?; |                 song.to_bytes(s)?; | ||||||
|             } |             } | ||||||
|             Self::AddAlbum(album) => { |             Self::AddAlbum(album) => { | ||||||
|                 s.write_all(&[0b01010011])?; |                 s.write_all(&[BYTE_ADD_ALBUM])?; | ||||||
|                 album.to_bytes(s)?; |                 album.to_bytes(s)?; | ||||||
|             } |             } | ||||||
|             Self::AddArtist(artist) => { |             Self::AddArtist(artist) => { | ||||||
|                 s.write_all(&[0b01011100])?; |                 s.write_all(&[BYTE_ADD_ARTIST])?; | ||||||
|                 artist.to_bytes(s)?; |                 artist.to_bytes(s)?; | ||||||
|             } |             } | ||||||
|             Self::AddCover(cover) => { |             Self::AddCover(cover) => { | ||||||
|                 s.write_all(&[0b01011101])?; |                 s.write_all(&[BYTE_ADD_COVER])?; | ||||||
|                 cover.to_bytes(s)?; |                 cover.to_bytes(s)?; | ||||||
|             } |             } | ||||||
|             Self::ModifySong(song) => { |             Self::ModifySong(song) => { | ||||||
|                 s.write_all(&[0b10010000])?; |                 s.write_all(&[BYTE_MODIFY_SONG])?; | ||||||
|                 song.to_bytes(s)?; |                 song.to_bytes(s)?; | ||||||
|             } |             } | ||||||
|             Self::ModifyAlbum(album) => { |             Self::ModifyAlbum(album) => { | ||||||
|                 s.write_all(&[0b10010011])?; |                 s.write_all(&[BYTE_MODIFY_ALBUM])?; | ||||||
|                 album.to_bytes(s)?; |                 album.to_bytes(s)?; | ||||||
|             } |             } | ||||||
|             Self::ModifyArtist(artist) => { |             Self::ModifyArtist(artist) => { | ||||||
|                 s.write_all(&[0b10011100])?; |                 s.write_all(&[BYTE_MODIFY_ARTIST])?; | ||||||
|                 artist.to_bytes(s)?; |                 artist.to_bytes(s)?; | ||||||
|             } |             } | ||||||
|             Self::RemoveSong(song) => { |             Self::RemoveSong(song) => { | ||||||
|                 s.write_all(&[0b11010000])?; |                 s.write_all(&[BYTE_REMOVE_SONG])?; | ||||||
|                 song.to_bytes(s)?; |                 song.to_bytes(s)?; | ||||||
|             } |             } | ||||||
|             Self::RemoveAlbum(album) => { |             Self::RemoveAlbum(album) => { | ||||||
|                 s.write_all(&[0b11010011])?; |                 s.write_all(&[BYTE_REMOVE_ALBUM])?; | ||||||
|                 album.to_bytes(s)?; |                 album.to_bytes(s)?; | ||||||
|             } |             } | ||||||
|             Self::RemoveArtist(artist) => { |             Self::RemoveArtist(artist) => { | ||||||
|                 s.write_all(&[0b11011100])?; |                 s.write_all(&[BYTE_REMOVE_ARTIST])?; | ||||||
|                 artist.to_bytes(s)?; |                 artist.to_bytes(s)?; | ||||||
|             } |             } | ||||||
|  |             Self::TagSongFlagSet(id, tag) => { | ||||||
|  |                 s.write_all(&[BYTE_TAG_SONG_FLAG_SET])?; | ||||||
|  |                 id.to_bytes(s)?; | ||||||
|  |                 tag.to_bytes(s)?; | ||||||
|  |             } | ||||||
|  |             Self::TagSongFlagUnset(id, tag) => { | ||||||
|  |                 s.write_all(&[BYTE_TAG_SONG_FLAG_UNSET])?; | ||||||
|  |                 id.to_bytes(s)?; | ||||||
|  |                 tag.to_bytes(s)?; | ||||||
|  |             } | ||||||
|  |             Self::TagAlbumFlagSet(id, tag) => { | ||||||
|  |                 s.write_all(&[BYTE_TAG_ALBUM_FLAG_SET])?; | ||||||
|  |                 id.to_bytes(s)?; | ||||||
|  |                 tag.to_bytes(s)?; | ||||||
|  |             } | ||||||
|  |             Self::TagAlbumFlagUnset(id, tag) => { | ||||||
|  |                 s.write_all(&[BYTE_TAG_ALBUM_FLAG_UNSET])?; | ||||||
|  |                 id.to_bytes(s)?; | ||||||
|  |                 tag.to_bytes(s)?; | ||||||
|  |             } | ||||||
|  |             Self::TagArtistFlagSet(id, tag) => { | ||||||
|  |                 s.write_all(&[BYTE_TAG_ARTIST_FLAG_SET])?; | ||||||
|  |                 id.to_bytes(s)?; | ||||||
|  |                 tag.to_bytes(s)?; | ||||||
|  |             } | ||||||
|  |             Self::TagArtistFlagUnset(id, tag) => { | ||||||
|  |                 s.write_all(&[BYTE_TAG_ARTIST_FLAG_UNSET])?; | ||||||
|  |                 id.to_bytes(s)?; | ||||||
|  |                 tag.to_bytes(s)?; | ||||||
|  |             } | ||||||
|  |             Self::TagSongPropertySet(id, key, val) => { | ||||||
|  |                 s.write_all(&[BYTE_TAG_SONG_PROPERTY_SET])?; | ||||||
|  |                 id.to_bytes(s)?; | ||||||
|  |                 key.to_bytes(s)?; | ||||||
|  |                 val.to_bytes(s)?; | ||||||
|  |             } | ||||||
|  |             Self::TagSongPropertyUnset(id, key) => { | ||||||
|  |                 s.write_all(&[BYTE_TAG_SONG_PROPERTY_UNSET])?; | ||||||
|  |                 id.to_bytes(s)?; | ||||||
|  |                 key.to_bytes(s)?; | ||||||
|  |             } | ||||||
|  |             Self::TagAlbumPropertySet(id, key, val) => { | ||||||
|  |                 s.write_all(&[BYTE_TAG_ALBUM_PROPERTY_SET])?; | ||||||
|  |                 id.to_bytes(s)?; | ||||||
|  |                 key.to_bytes(s)?; | ||||||
|  |                 val.to_bytes(s)?; | ||||||
|  |             } | ||||||
|  |             Self::TagAlbumPropertyUnset(id, key) => { | ||||||
|  |                 s.write_all(&[BYTE_TAG_ALBUM_PROPERTY_UNSET])?; | ||||||
|  |                 id.to_bytes(s)?; | ||||||
|  |                 key.to_bytes(s)?; | ||||||
|  |             } | ||||||
|  |             Self::TagArtistPropertySet(id, key, val) => { | ||||||
|  |                 s.write_all(&[BYTE_TAG_ARTIST_PROPERTY_SET])?; | ||||||
|  |                 id.to_bytes(s)?; | ||||||
|  |                 key.to_bytes(s)?; | ||||||
|  |                 val.to_bytes(s)?; | ||||||
|  |             } | ||||||
|  |             Self::TagArtistPropertyUnset(id, key) => { | ||||||
|  |                 s.write_all(&[BYTE_TAG_ARTIST_PROPERTY_UNSET])?; | ||||||
|  |                 id.to_bytes(s)?; | ||||||
|  |                 key.to_bytes(s)?; | ||||||
|  |             } | ||||||
|             Self::SetSongDuration(i, d) => { |             Self::SetSongDuration(i, d) => { | ||||||
|                 s.write_all(&[0b11100000])?; |                 s.write_all(&[BYTE_SET_SONG_DURATION])?; | ||||||
|                 i.to_bytes(s)?; |                 i.to_bytes(s)?; | ||||||
|                 d.to_bytes(s)?; |                 d.to_bytes(s)?; | ||||||
|             } |             } | ||||||
|             Self::InitComplete => { |             Self::InitComplete => { | ||||||
|                 s.write_all(&[0b00110001])?; |                 s.write_all(&[BYTE_INIT_COMPLETE])?; | ||||||
|             } |             } | ||||||
|  |             Self::Save => s.write_all(&[BYTE_SAVE])?, | ||||||
|             Self::ErrorInfo(t, d) => { |             Self::ErrorInfo(t, d) => { | ||||||
|                 s.write_all(&[0b11011011])?; |                 s.write_all(&[BYTE_ERRORINFO])?; | ||||||
|                 t.to_bytes(s)?; |                 t.to_bytes(s)?; | ||||||
|                 d.to_bytes(s)?; |                 d.to_bytes(s)?; | ||||||
|             } |             } | ||||||
| @ -303,46 +428,61 @@ impl ToFromBytes for Command { | |||||||
|     { |     { | ||||||
|         let mut kind = [0]; |         let mut kind = [0]; | ||||||
|         s.read_exact(&mut kind)?; |         s.read_exact(&mut kind)?; | ||||||
|  |         macro_rules! from_bytes { | ||||||
|  |             () => { | ||||||
|  |                 ToFromBytes::from_bytes(s)? | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|         Ok(match kind[0] { |         Ok(match kind[0] { | ||||||
|             0b11000000 => Self::Resume, |             BYTE_RESUME => Self::Resume, | ||||||
|             0b00110000 => Self::Pause, |             BYTE_PAUSE => Self::Pause, | ||||||
|             0b11110000 => Self::Stop, |             BYTE_STOP => Self::Stop, | ||||||
|             0b11110011 => Self::Save, |             BYTE_NEXT_SONG => Self::NextSong, | ||||||
|             0b11110010 => Self::NextSong, |             BYTE_SYNC_DATABASE => Self::SyncDatabase(from_bytes!(), from_bytes!(), from_bytes!()), | ||||||
|             0b01011000 => Self::SyncDatabase( |             BYTE_QUEUE_UPDATE => Self::QueueUpdate(from_bytes!(), from_bytes!()), | ||||||
|                 ToFromBytes::from_bytes(s)?, |             BYTE_QUEUE_ADD => Self::QueueAdd(from_bytes!(), from_bytes!()), | ||||||
|                 ToFromBytes::from_bytes(s)?, |             BYTE_QUEUE_INSERT => Self::QueueInsert(from_bytes!(), from_bytes!(), from_bytes!()), | ||||||
|                 ToFromBytes::from_bytes(s)?, |             BYTE_QUEUE_REMOVE => Self::QueueRemove(from_bytes!()), | ||||||
|             ), |             BYTE_QUEUE_GOTO => Self::QueueGoto(from_bytes!()), | ||||||
|             0b00011100 => { |             BYTE_QUEUE_SET_SHUFFLE => Self::QueueSetShuffle(from_bytes!(), from_bytes!()), | ||||||
|                 Self::QueueUpdate(ToFromBytes::from_bytes(s)?, ToFromBytes::from_bytes(s)?) |             BYTE_ADD_SONG => Self::AddSong(from_bytes!()), | ||||||
|  |             BYTE_ADD_ALBUM => Self::AddAlbum(from_bytes!()), | ||||||
|  |             BYTE_ADD_ARTIST => Self::AddArtist(from_bytes!()), | ||||||
|  |             BYTE_MODIFY_SONG => Self::ModifySong(from_bytes!()), | ||||||
|  |             BYTE_MODIFY_ALBUM => Self::ModifyAlbum(from_bytes!()), | ||||||
|  |             BYTE_MODIFY_ARTIST => Self::ModifyArtist(from_bytes!()), | ||||||
|  |             BYTE_REMOVE_SONG => Self::RemoveSong(from_bytes!()), | ||||||
|  |             BYTE_REMOVE_ALBUM => Self::RemoveAlbum(from_bytes!()), | ||||||
|  |             BYTE_REMOVE_ARTIST => Self::RemoveArtist(from_bytes!()), | ||||||
|  |             BYTE_TAG_SONG_FLAG_SET => Self::TagSongFlagSet(from_bytes!(), from_bytes!()), | ||||||
|  |             BYTE_TAG_SONG_FLAG_UNSET => Self::TagSongFlagUnset(from_bytes!(), from_bytes!()), | ||||||
|  |             BYTE_TAG_ALBUM_FLAG_SET => Self::TagAlbumFlagSet(from_bytes!(), from_bytes!()), | ||||||
|  |             BYTE_TAG_ALBUM_FLAG_UNSET => Self::TagAlbumFlagUnset(from_bytes!(), from_bytes!()), | ||||||
|  |             BYTE_TAG_ARTIST_FLAG_SET => Self::TagArtistFlagSet(from_bytes!(), from_bytes!()), | ||||||
|  |             BYTE_TAG_ARTIST_FLAG_UNSET => Self::TagArtistFlagUnset(from_bytes!(), from_bytes!()), | ||||||
|  |             BYTE_TAG_SONG_PROPERTY_SET => { | ||||||
|  |                 Self::TagSongPropertySet(from_bytes!(), from_bytes!(), from_bytes!()) | ||||||
|             } |             } | ||||||
|             0b00011010 => Self::QueueAdd(ToFromBytes::from_bytes(s)?, ToFromBytes::from_bytes(s)?), |             BYTE_TAG_SONG_PROPERTY_UNSET => { | ||||||
|             0b00011110 => Self::QueueInsert( |                 Self::TagSongPropertyUnset(from_bytes!(), from_bytes!()) | ||||||
|                 ToFromBytes::from_bytes(s)?, |  | ||||||
|                 ToFromBytes::from_bytes(s)?, |  | ||||||
|                 ToFromBytes::from_bytes(s)?, |  | ||||||
|             ), |  | ||||||
|             0b00011001 => Self::QueueRemove(ToFromBytes::from_bytes(s)?), |  | ||||||
|             0b00011011 => Self::QueueGoto(ToFromBytes::from_bytes(s)?), |  | ||||||
|             0b10011011 => { |  | ||||||
|                 Self::QueueSetShuffle(ToFromBytes::from_bytes(s)?, ToFromBytes::from_bytes(s)?) |  | ||||||
|             } |             } | ||||||
|             0b01010000 => Self::AddSong(ToFromBytes::from_bytes(s)?), |             BYTE_TAG_ALBUM_PROPERTY_SET => { | ||||||
|             0b01010011 => Self::AddAlbum(ToFromBytes::from_bytes(s)?), |                 Self::TagAlbumPropertySet(from_bytes!(), from_bytes!(), from_bytes!()) | ||||||
|             0b01011100 => Self::AddArtist(ToFromBytes::from_bytes(s)?), |  | ||||||
|             0b10010000 => Self::ModifySong(ToFromBytes::from_bytes(s)?), |  | ||||||
|             0b10010011 => Self::ModifyAlbum(ToFromBytes::from_bytes(s)?), |  | ||||||
|             0b10011100 => Self::ModifyArtist(ToFromBytes::from_bytes(s)?), |  | ||||||
|             0b11010000 => Self::RemoveSong(ToFromBytes::from_bytes(s)?), |  | ||||||
|             0b11010011 => Self::RemoveAlbum(ToFromBytes::from_bytes(s)?), |  | ||||||
|             0b11011100 => Self::RemoveArtist(ToFromBytes::from_bytes(s)?), |  | ||||||
|             0b11100000 => { |  | ||||||
|                 Self::SetSongDuration(ToFromBytes::from_bytes(s)?, ToFromBytes::from_bytes(s)?) |  | ||||||
|             } |             } | ||||||
|             0b01011101 => Self::AddCover(ToFromBytes::from_bytes(s)?), |             BYTE_TAG_ALBUM_PROPERTY_UNSET => { | ||||||
|             0b00110001 => Self::InitComplete, |                 Self::TagAlbumPropertyUnset(from_bytes!(), from_bytes!()) | ||||||
|             0b11011011 => Self::ErrorInfo(ToFromBytes::from_bytes(s)?, ToFromBytes::from_bytes(s)?), |             } | ||||||
|  |             BYTE_TAG_ARTIST_PROPERTY_SET => { | ||||||
|  |                 Self::TagArtistPropertySet(from_bytes!(), from_bytes!(), from_bytes!()) | ||||||
|  |             } | ||||||
|  |             BYTE_TAG_ARTIST_PROPERTY_UNSET => { | ||||||
|  |                 Self::TagArtistPropertyUnset(from_bytes!(), from_bytes!()) | ||||||
|  |             } | ||||||
|  |             BYTE_SET_SONG_DURATION => Self::SetSongDuration(from_bytes!(), from_bytes!()), | ||||||
|  |             BYTE_ADD_COVER => Self::AddCover(from_bytes!()), | ||||||
|  |             BYTE_INIT_COMPLETE => Self::InitComplete, | ||||||
|  |             BYTE_SAVE => Self::Save, | ||||||
|  |             BYTE_ERRORINFO => Self::ErrorInfo(from_bytes!(), from_bytes!()), | ||||||
|             _ => { |             _ => { | ||||||
|                 eprintln!("unexpected byte when reading command; stopping playback."); |                 eprintln!("unexpected byte when reading command; stopping playback."); | ||||||
|                 Self::Stop |                 Self::Stop | ||||||
|  | |||||||
| @ -354,6 +354,18 @@ async fn sse_handler( | |||||||
|                 | Command::RemoveSong(_) |                 | Command::RemoveSong(_) | ||||||
|                 | Command::RemoveAlbum(_) |                 | Command::RemoveAlbum(_) | ||||||
|                 | Command::RemoveArtist(_) |                 | Command::RemoveArtist(_) | ||||||
|  |                 | Command::TagSongFlagSet(..) | ||||||
|  |                 | Command::TagSongFlagUnset(..) | ||||||
|  |                 | Command::TagAlbumFlagSet(..) | ||||||
|  |                 | Command::TagAlbumFlagUnset(..) | ||||||
|  |                 | Command::TagArtistFlagSet(..) | ||||||
|  |                 | Command::TagArtistFlagUnset(..) | ||||||
|  |                 | Command::TagSongPropertySet(..) | ||||||
|  |                 | Command::TagSongPropertyUnset(..) | ||||||
|  |                 | Command::TagAlbumPropertySet(..) | ||||||
|  |                 | Command::TagAlbumPropertyUnset(..) | ||||||
|  |                 | Command::TagArtistPropertySet(..) | ||||||
|  |                 | Command::TagArtistPropertyUnset(..) | ||||||
|                 | Command::SetSongDuration(..) => Event::default().event("artists").data({ |                 | Command::SetSongDuration(..) => Event::default().event("artists").data({ | ||||||
|                     let db = state.db.lock().unwrap(); |                     let db = state.db.lock().unwrap(); | ||||||
|                     let mut a = db.artists().iter().collect::<Vec<_>>(); |                     let mut a = db.artists().iter().collect::<Vec<_>>(); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Mark
						Mark