mirror of
https://github.com/Dummi26/musicdb.git
synced 2025-03-10 05:43:53 +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…
Reference in New Issue
Block a user