mirror of
https://github.com/Dummi26/musicdb.git
synced 2025-03-10 05:43:53 +01:00
server can now send error messages to clients
This commit is contained in:
parent
94df757f0c
commit
1eee22bb4b
@ -6,7 +6,7 @@ use std::{
|
|||||||
net::TcpStream,
|
net::TcpStream,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
thread::JoinHandle,
|
thread::JoinHandle,
|
||||||
time::Instant,
|
time::{Duration, Instant},
|
||||||
usize,
|
usize,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -28,7 +28,14 @@ use speedy2d::{
|
|||||||
Graphics2D,
|
Graphics2D,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{gui_screen::GuiScreen, gui_wrappers::WithFocusHotkey, textcfg};
|
use crate::{
|
||||||
|
gui_base::Panel,
|
||||||
|
gui_notif::{NotifInfo, NotifOverlay},
|
||||||
|
gui_screen::GuiScreen,
|
||||||
|
gui_text::Label,
|
||||||
|
gui_wrappers::WithFocusHotkey,
|
||||||
|
textcfg,
|
||||||
|
};
|
||||||
|
|
||||||
pub enum GuiEvent {
|
pub enum GuiEvent {
|
||||||
Refresh,
|
Refresh,
|
||||||
@ -192,6 +199,7 @@ impl Gui {
|
|||||||
scroll_pages_multiplier: f64,
|
scroll_pages_multiplier: f64,
|
||||||
gui_config: GuiConfig,
|
gui_config: GuiConfig,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let (notif_overlay, notif_sender) = NotifOverlay::new();
|
||||||
database.lock().unwrap().update_endpoints.push(
|
database.lock().unwrap().update_endpoints.push(
|
||||||
musicdb_lib::data::database::UpdateEndpoint::Custom(Box::new(move |cmd| match cmd {
|
musicdb_lib::data::database::UpdateEndpoint::Custom(Box::new(move |cmd| match cmd {
|
||||||
Command::Resume
|
Command::Resume
|
||||||
@ -225,6 +233,37 @@ impl Gui {
|
|||||||
_ = s.send_event(GuiEvent::UpdatedLibrary);
|
_ = s.send_event(GuiEvent::UpdatedLibrary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Command::ErrorInfo(t, d) => {
|
||||||
|
eprintln!("{t:?} | {d:?}");
|
||||||
|
let (t, d) = (t.clone(), d.clone());
|
||||||
|
notif_sender
|
||||||
|
.send(Box::new(move |_| {
|
||||||
|
(
|
||||||
|
GuiElem::new(Panel::with_background(
|
||||||
|
GuiElemCfg::default(),
|
||||||
|
vec![GuiElem::new(Label::new(
|
||||||
|
GuiElemCfg::default(),
|
||||||
|
if t.is_empty() {
|
||||||
|
format!("Server message\n{d}")
|
||||||
|
} else {
|
||||||
|
format!("Server error ({t})\n{d}")
|
||||||
|
},
|
||||||
|
Color::WHITE,
|
||||||
|
None,
|
||||||
|
Vec2::new(0.5, 0.5),
|
||||||
|
))],
|
||||||
|
Color::from_rgba(0.0, 0.0, 0.0, 0.8),
|
||||||
|
)),
|
||||||
|
if t.is_empty() {
|
||||||
|
NotifInfo::new(Duration::from_secs(2))
|
||||||
|
} else {
|
||||||
|
NotifInfo::new(Duration::from_secs(5))
|
||||||
|
.with_highlight(Color::RED)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
Gui {
|
Gui {
|
||||||
@ -236,6 +275,7 @@ impl Gui {
|
|||||||
VirtualKeyCode::Escape,
|
VirtualKeyCode::Escape,
|
||||||
GuiScreen::new(
|
GuiScreen::new(
|
||||||
GuiElemCfg::default(),
|
GuiElemCfg::default(),
|
||||||
|
notif_overlay,
|
||||||
line_height,
|
line_height,
|
||||||
scroll_pixels_multiplier,
|
scroll_pixels_multiplier,
|
||||||
scroll_lines_multiplier,
|
scroll_lines_multiplier,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::{
|
use std::{
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
rc::Rc,
|
sync::Arc,
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, AtomicUsize},
|
atomic::{AtomicBool, AtomicUsize},
|
||||||
mpsc, Mutex,
|
mpsc, Mutex,
|
||||||
@ -57,17 +57,17 @@ pub struct LibraryBrowser {
|
|||||||
search_album_regex: Option<Regex>,
|
search_album_regex: Option<Regex>,
|
||||||
search_song: String,
|
search_song: String,
|
||||||
search_song_regex: Option<Regex>,
|
search_song_regex: Option<Regex>,
|
||||||
filter_target_state: Rc<AtomicBool>,
|
filter_target_state: Arc<AtomicBool>,
|
||||||
filter_state: f32,
|
filter_state: f32,
|
||||||
library_updated: bool,
|
library_updated: bool,
|
||||||
search_settings_changed: Rc<AtomicBool>,
|
search_settings_changed: Arc<AtomicBool>,
|
||||||
search_is_case_sensitive: Rc<AtomicBool>,
|
search_is_case_sensitive: Arc<AtomicBool>,
|
||||||
search_was_case_sensitive: bool,
|
search_was_case_sensitive: bool,
|
||||||
search_prefer_start_matches: Rc<AtomicBool>,
|
search_prefer_start_matches: Arc<AtomicBool>,
|
||||||
search_prefers_start_matches: bool,
|
search_prefers_start_matches: bool,
|
||||||
filter_songs: Rc<Mutex<Filter>>,
|
filter_songs: Arc<Mutex<Filter>>,
|
||||||
filter_albums: Rc<Mutex<Filter>>,
|
filter_albums: Arc<Mutex<Filter>>,
|
||||||
filter_artists: Rc<Mutex<Filter>>,
|
filter_artists: Arc<Mutex<Filter>>,
|
||||||
do_something_receiver: mpsc::Receiver<Box<dyn FnOnce(&mut Self)>>,
|
do_something_receiver: mpsc::Receiver<Box<dyn FnOnce(&mut Self)>>,
|
||||||
}
|
}
|
||||||
impl Clone for LibraryBrowser {
|
impl Clone for LibraryBrowser {
|
||||||
@ -76,7 +76,7 @@ impl Clone for LibraryBrowser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct Selected(Rc<Mutex<(HashSet<ArtistId>, HashSet<AlbumId>, HashSet<SongId>)>>);
|
struct Selected(Arc<Mutex<(HashSet<ArtistId>, HashSet<AlbumId>, HashSet<SongId>)>>);
|
||||||
impl Selected {
|
impl Selected {
|
||||||
pub fn as_queue(&self, lb: &LibraryBrowser, db: &Database) -> Vec<Queue> {
|
pub fn as_queue(&self, lb: &LibraryBrowser, db: &Database) -> Vec<Queue> {
|
||||||
let lock = self.0.lock().unwrap();
|
let lock = self.0.lock().unwrap();
|
||||||
@ -185,13 +185,13 @@ impl LibraryBrowser {
|
|||||||
vec![],
|
vec![],
|
||||||
);
|
);
|
||||||
let (do_something_sender, do_something_receiver) = mpsc::channel();
|
let (do_something_sender, do_something_receiver) = mpsc::channel();
|
||||||
let search_settings_changed = Rc::new(AtomicBool::new(false));
|
let search_settings_changed = Arc::new(AtomicBool::new(false));
|
||||||
let search_was_case_sensitive = false;
|
let search_was_case_sensitive = false;
|
||||||
let search_is_case_sensitive = Rc::new(AtomicBool::new(search_was_case_sensitive));
|
let search_is_case_sensitive = Arc::new(AtomicBool::new(search_was_case_sensitive));
|
||||||
let search_prefers_start_matches = true;
|
let search_prefers_start_matches = true;
|
||||||
let search_prefer_start_matches = Rc::new(AtomicBool::new(search_prefers_start_matches));
|
let search_prefer_start_matches = Arc::new(AtomicBool::new(search_prefers_start_matches));
|
||||||
let filter_target_state = Rc::new(AtomicBool::new(false));
|
let filter_target_state = Arc::new(AtomicBool::new(false));
|
||||||
let fts = Rc::clone(&filter_target_state);
|
let fts = Arc::clone(&filter_target_state);
|
||||||
let filter_button = Button::new(
|
let filter_button = Button::new(
|
||||||
GuiElemCfg::at(Rectangle::from_tuples((0.46, 0.01), (0.54, 0.05))),
|
GuiElemCfg::at(Rectangle::from_tuples((0.46, 0.01), (0.54, 0.05))),
|
||||||
move |_| {
|
move |_| {
|
||||||
@ -209,19 +209,19 @@ impl LibraryBrowser {
|
|||||||
Vec2::new(0.5, 0.5),
|
Vec2::new(0.5, 0.5),
|
||||||
))],
|
))],
|
||||||
);
|
);
|
||||||
let filter_songs = Rc::new(Mutex::new(Filter {
|
let filter_songs = Arc::new(Mutex::new(Filter {
|
||||||
and: true,
|
and: true,
|
||||||
filters: vec![],
|
filters: vec![],
|
||||||
}));
|
}));
|
||||||
let filter_albums = Rc::new(Mutex::new(Filter {
|
let filter_albums = Arc::new(Mutex::new(Filter {
|
||||||
and: true,
|
and: true,
|
||||||
filters: vec![],
|
filters: vec![],
|
||||||
}));
|
}));
|
||||||
let filter_artists = Rc::new(Mutex::new(Filter {
|
let filter_artists = Arc::new(Mutex::new(Filter {
|
||||||
and: true,
|
and: true,
|
||||||
filters: vec![],
|
filters: vec![],
|
||||||
}));
|
}));
|
||||||
let selected = Selected(Rc::new(Mutex::new((
|
let selected = Selected(Arc::new(Mutex::new((
|
||||||
HashSet::new(),
|
HashSet::new(),
|
||||||
HashSet::new(),
|
HashSet::new(),
|
||||||
HashSet::new(),
|
HashSet::new(),
|
||||||
@ -235,12 +235,12 @@ impl LibraryBrowser {
|
|||||||
GuiElem::new(library_scroll_box),
|
GuiElem::new(library_scroll_box),
|
||||||
GuiElem::new(filter_button),
|
GuiElem::new(filter_button),
|
||||||
GuiElem::new(FilterPanel::new(
|
GuiElem::new(FilterPanel::new(
|
||||||
Rc::clone(&search_settings_changed),
|
Arc::clone(&search_settings_changed),
|
||||||
Rc::clone(&search_is_case_sensitive),
|
Arc::clone(&search_is_case_sensitive),
|
||||||
Rc::clone(&search_prefer_start_matches),
|
Arc::clone(&search_prefer_start_matches),
|
||||||
Rc::clone(&filter_songs),
|
Arc::clone(&filter_songs),
|
||||||
Rc::clone(&filter_albums),
|
Arc::clone(&filter_albums),
|
||||||
Rc::clone(&filter_artists),
|
Arc::clone(&filter_artists),
|
||||||
selected.clone(),
|
selected.clone(),
|
||||||
do_something_sender.clone(),
|
do_something_sender.clone(),
|
||||||
)),
|
)),
|
||||||
@ -1137,13 +1137,13 @@ impl GuiElemTrait for ListSong {
|
|||||||
struct FilterPanel {
|
struct FilterPanel {
|
||||||
config: GuiElemCfg,
|
config: GuiElemCfg,
|
||||||
children: Vec<GuiElem>,
|
children: Vec<GuiElem>,
|
||||||
search_settings_changed: Rc<AtomicBool>,
|
search_settings_changed: Arc<AtomicBool>,
|
||||||
tab: usize,
|
tab: usize,
|
||||||
new_tab: Rc<AtomicUsize>,
|
new_tab: Arc<AtomicUsize>,
|
||||||
line_height: f32,
|
line_height: f32,
|
||||||
filter_songs: Rc<Mutex<Filter>>,
|
filter_songs: Arc<Mutex<Filter>>,
|
||||||
filter_albums: Rc<Mutex<Filter>>,
|
filter_albums: Arc<Mutex<Filter>>,
|
||||||
filter_artists: Rc<Mutex<Filter>>,
|
filter_artists: Arc<Mutex<Filter>>,
|
||||||
}
|
}
|
||||||
const FP_CASESENS_N: &'static str = "search is case-insensitive";
|
const FP_CASESENS_N: &'static str = "search is case-insensitive";
|
||||||
const FP_CASESENS_Y: &'static str = "search is case-sensitive!";
|
const FP_CASESENS_Y: &'static str = "search is case-sensitive!";
|
||||||
@ -1151,21 +1151,21 @@ const FP_PREFSTART_N: &'static str = "simple search";
|
|||||||
const FP_PREFSTART_Y: &'static str = "will prefer matches at the start of a word";
|
const FP_PREFSTART_Y: &'static str = "will prefer matches at the start of a word";
|
||||||
impl FilterPanel {
|
impl FilterPanel {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
search_settings_changed: Rc<AtomicBool>,
|
search_settings_changed: Arc<AtomicBool>,
|
||||||
search_is_case_sensitive: Rc<AtomicBool>,
|
search_is_case_sensitive: Arc<AtomicBool>,
|
||||||
search_prefer_start_matches: Rc<AtomicBool>,
|
search_prefer_start_matches: Arc<AtomicBool>,
|
||||||
filter_songs: Rc<Mutex<Filter>>,
|
filter_songs: Arc<Mutex<Filter>>,
|
||||||
filter_albums: Rc<Mutex<Filter>>,
|
filter_albums: Arc<Mutex<Filter>>,
|
||||||
filter_artists: Rc<Mutex<Filter>>,
|
filter_artists: Arc<Mutex<Filter>>,
|
||||||
selected: Selected,
|
selected: Selected,
|
||||||
do_something_sender: mpsc::Sender<Box<dyn FnOnce(&mut LibraryBrowser)>>,
|
do_something_sender: mpsc::Sender<Box<dyn FnOnce(&mut LibraryBrowser)>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let is_case_sensitive = search_is_case_sensitive.load(std::sync::atomic::Ordering::Relaxed);
|
let is_case_sensitive = search_is_case_sensitive.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
let prefer_start_matches =
|
let prefer_start_matches =
|
||||||
search_prefer_start_matches.load(std::sync::atomic::Ordering::Relaxed);
|
search_prefer_start_matches.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
let ssc1 = Rc::clone(&search_settings_changed);
|
let ssc1 = Arc::clone(&search_settings_changed);
|
||||||
let ssc2 = Rc::clone(&search_settings_changed);
|
let ssc2 = Arc::clone(&search_settings_changed);
|
||||||
let ssc3 = Rc::clone(&search_settings_changed);
|
let ssc3 = Arc::clone(&search_settings_changed);
|
||||||
let sel3 = selected.clone();
|
let sel3 = selected.clone();
|
||||||
const VSPLIT: f32 = 0.4;
|
const VSPLIT: f32 = 0.4;
|
||||||
let tab_main = GuiElem::new(ScrollBox::new(
|
let tab_main = GuiElem::new(ScrollBox::new(
|
||||||
@ -1387,10 +1387,10 @@ impl FilterPanel {
|
|||||||
crate::gui_base::ScrollBoxSizeUnit::Pixels,
|
crate::gui_base::ScrollBoxSizeUnit::Pixels,
|
||||||
vec![],
|
vec![],
|
||||||
));
|
));
|
||||||
let new_tab = Rc::new(AtomicUsize::new(0));
|
let new_tab = Arc::new(AtomicUsize::new(0));
|
||||||
let set_tab_1 = Rc::clone(&new_tab);
|
let set_tab_1 = Arc::clone(&new_tab);
|
||||||
let set_tab_2 = Rc::clone(&new_tab);
|
let set_tab_2 = Arc::clone(&new_tab);
|
||||||
let set_tab_3 = Rc::clone(&new_tab);
|
let set_tab_3 = Arc::clone(&new_tab);
|
||||||
const HEIGHT: f32 = 0.1;
|
const HEIGHT: f32 = 0.1;
|
||||||
Self {
|
Self {
|
||||||
config: GuiElemCfg::default().disabled(),
|
config: GuiElemCfg::default().disabled(),
|
||||||
@ -1458,17 +1458,17 @@ impl FilterPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn build_filter(
|
fn build_filter(
|
||||||
filter: &Rc<Mutex<Filter>>,
|
filter: &Arc<Mutex<Filter>>,
|
||||||
line_height: f32,
|
line_height: f32,
|
||||||
on_change: &Rc<impl Fn(bool) + 'static>,
|
on_change: &Arc<impl Fn(bool) + 'static>,
|
||||||
path: Vec<usize>,
|
path: Vec<usize>,
|
||||||
) -> Vec<(GuiElem, f32)> {
|
) -> Vec<(GuiElem, f32)> {
|
||||||
let f0 = Rc::clone(filter);
|
let f0 = Arc::clone(filter);
|
||||||
let oc0 = Rc::clone(on_change);
|
let oc0 = Arc::clone(on_change);
|
||||||
let f1 = Rc::clone(filter);
|
let f1 = Arc::clone(filter);
|
||||||
let f2 = Rc::clone(filter);
|
let f2 = Arc::clone(filter);
|
||||||
let oc1 = Rc::clone(on_change);
|
let oc1 = Arc::clone(on_change);
|
||||||
let oc2 = Rc::clone(on_change);
|
let oc2 = Arc::clone(on_change);
|
||||||
let mut children = vec![
|
let mut children = vec![
|
||||||
GuiElem::new(Button::new(
|
GuiElem::new(Button::new(
|
||||||
GuiElemCfg::default(),
|
GuiElemCfg::default(),
|
||||||
@ -1536,16 +1536,16 @@ impl FilterPanel {
|
|||||||
}
|
}
|
||||||
fn build_filter_editor(
|
fn build_filter_editor(
|
||||||
filter: &Filter,
|
filter: &Filter,
|
||||||
mutex: &Rc<Mutex<Filter>>,
|
mutex: &Arc<Mutex<Filter>>,
|
||||||
children: &mut Vec<GuiElem>,
|
children: &mut Vec<GuiElem>,
|
||||||
mut indent: f32,
|
mut indent: f32,
|
||||||
indent_by: f32,
|
indent_by: f32,
|
||||||
on_change: &Rc<impl Fn(bool) + 'static>,
|
on_change: &Arc<impl Fn(bool) + 'static>,
|
||||||
path: Vec<usize>,
|
path: Vec<usize>,
|
||||||
) {
|
) {
|
||||||
if filter.filters.len() > 1 {
|
if filter.filters.len() > 1 {
|
||||||
let mx = Rc::clone(mutex);
|
let mx = Arc::clone(mutex);
|
||||||
let oc = Rc::clone(on_change);
|
let oc = Arc::clone(on_change);
|
||||||
let p = path.clone();
|
let p = path.clone();
|
||||||
children.push(GuiElem::new(Button::new(
|
children.push(GuiElem::new(Button::new(
|
||||||
GuiElemCfg::at(Rectangle::from_tuples((indent, 0.0), (1.0, 1.0))),
|
GuiElemCfg::at(Rectangle::from_tuples((indent, 0.0), (1.0, 1.0))),
|
||||||
@ -1597,8 +1597,8 @@ impl FilterPanel {
|
|||||||
Color::GRAY,
|
Color::GRAY,
|
||||||
Color::WHITE,
|
Color::WHITE,
|
||||||
);
|
);
|
||||||
let mx = Rc::clone(mutex);
|
let mx = Arc::clone(mutex);
|
||||||
let oc = Rc::clone(on_change);
|
let oc = Arc::clone(on_change);
|
||||||
tf.on_changed = Some(Box::new(move |text| {
|
tf.on_changed = Some(Box::new(move |text| {
|
||||||
if let Some(Ok(FilterType::TagEq(v))) = mx.lock().unwrap().get_mut(&path) {
|
if let Some(Ok(FilterType::TagEq(v))) = mx.lock().unwrap().get_mut(&path) {
|
||||||
*v = text.to_owned();
|
*v = text.to_owned();
|
||||||
@ -1627,8 +1627,8 @@ impl FilterPanel {
|
|||||||
Color::GRAY,
|
Color::GRAY,
|
||||||
Color::WHITE,
|
Color::WHITE,
|
||||||
);
|
);
|
||||||
let mx = Rc::clone(mutex);
|
let mx = Arc::clone(mutex);
|
||||||
let oc = Rc::clone(on_change);
|
let oc = Arc::clone(on_change);
|
||||||
tf.on_changed = Some(Box::new(move |text| {
|
tf.on_changed = Some(Box::new(move |text| {
|
||||||
if let Some(Ok(FilterType::TagStartsWith(v))) =
|
if let Some(Ok(FilterType::TagStartsWith(v))) =
|
||||||
mx.lock().unwrap().get_mut(&path)
|
mx.lock().unwrap().get_mut(&path)
|
||||||
@ -1659,8 +1659,8 @@ impl FilterPanel {
|
|||||||
Color::GRAY,
|
Color::GRAY,
|
||||||
Color::WHITE,
|
Color::WHITE,
|
||||||
);
|
);
|
||||||
let mx = Rc::clone(mutex);
|
let mx = Arc::clone(mutex);
|
||||||
let oc = Rc::clone(on_change);
|
let oc = Arc::clone(on_change);
|
||||||
let p = path.clone();
|
let p = path.clone();
|
||||||
tf.on_changed = Some(Box::new(move |text| {
|
tf.on_changed = Some(Box::new(move |text| {
|
||||||
if let Some(Ok(FilterType::TagWithValueInt(v, _, _))) =
|
if let Some(Ok(FilterType::TagWithValueInt(v, _, _))) =
|
||||||
@ -1684,8 +1684,8 @@ impl FilterPanel {
|
|||||||
Color::GRAY,
|
Color::GRAY,
|
||||||
Color::WHITE,
|
Color::WHITE,
|
||||||
);
|
);
|
||||||
let mx = Rc::clone(mutex);
|
let mx = Arc::clone(mutex);
|
||||||
let oc = Rc::clone(on_change);
|
let oc = Arc::clone(on_change);
|
||||||
let p = path.clone();
|
let p = path.clone();
|
||||||
tf1.on_changed = Some(Box::new(move |text| {
|
tf1.on_changed = Some(Box::new(move |text| {
|
||||||
if let Ok(n) = text.parse() {
|
if let Ok(n) = text.parse() {
|
||||||
@ -1697,8 +1697,8 @@ impl FilterPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
let mx = Rc::clone(mutex);
|
let mx = Arc::clone(mutex);
|
||||||
let oc = Rc::clone(on_change);
|
let oc = Arc::clone(on_change);
|
||||||
let p = path.clone();
|
let p = path.clone();
|
||||||
tf2.on_changed = Some(Box::new(move |text| {
|
tf2.on_changed = Some(Box::new(move |text| {
|
||||||
if let Ok(n) = text.parse() {
|
if let Ok(n) = text.parse() {
|
||||||
@ -1813,9 +1813,9 @@ impl GuiElemTrait for FilterPanel {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.try_as_mut::<ScrollBox>()
|
.try_as_mut::<ScrollBox>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let ssc = Rc::clone(&self.search_settings_changed);
|
let ssc = Arc::clone(&self.search_settings_changed);
|
||||||
let my_tab = new_tab;
|
let my_tab = new_tab;
|
||||||
let ntab = Rc::clone(&self.new_tab);
|
let ntab = Arc::clone(&self.new_tab);
|
||||||
sb.children = Self::build_filter(
|
sb.children = Self::build_filter(
|
||||||
match new_tab {
|
match new_tab {
|
||||||
0 => &self.filter_songs,
|
0 => &self.filter_songs,
|
||||||
@ -1824,7 +1824,7 @@ impl GuiElemTrait for FilterPanel {
|
|||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
},
|
},
|
||||||
info.line_height,
|
info.line_height,
|
||||||
&Rc::new(move |update_ui| {
|
&Arc::new(move |update_ui| {
|
||||||
if update_ui {
|
if update_ui {
|
||||||
ntab.store(my_tab, std::sync::atomic::Ordering::Relaxed);
|
ntab.store(my_tab, std::sync::atomic::Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
209
musicdb-client/src/gui_notif.rs
Normal file
209
musicdb-client/src/gui_notif.rs
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
use std::{
|
||||||
|
sync::mpsc,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
use speedy2d::{color::Color, dimen::Vector2, shape::Rectangle};
|
||||||
|
|
||||||
|
use crate::gui::{GuiElem, GuiElemCfg, GuiElemTrait};
|
||||||
|
|
||||||
|
/// This should be added on top of overything else and set to fullscreen.
|
||||||
|
/// It will respond to notification events.
|
||||||
|
pub struct NotifOverlay {
|
||||||
|
config: GuiElemCfg,
|
||||||
|
notifs: Vec<(GuiElem, NotifInfo)>,
|
||||||
|
light: Option<(Instant, Color)>,
|
||||||
|
receiver: mpsc::Receiver<Box<dyn FnOnce(&Self) -> (GuiElem, NotifInfo) + Send>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NotifOverlay {
|
||||||
|
pub fn new() -> (
|
||||||
|
Self,
|
||||||
|
mpsc::Sender<Box<dyn FnOnce(&Self) -> (GuiElem, NotifInfo) + Send>>,
|
||||||
|
) {
|
||||||
|
let (sender, receiver) = mpsc::channel();
|
||||||
|
(
|
||||||
|
Self {
|
||||||
|
config: GuiElemCfg::default(),
|
||||||
|
notifs: vec![],
|
||||||
|
light: None,
|
||||||
|
receiver,
|
||||||
|
},
|
||||||
|
sender,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_notifs(&mut self) {
|
||||||
|
let mut adjust_heights = false;
|
||||||
|
let mut remove = Vec::with_capacity(0);
|
||||||
|
for (i, (gui, info)) in self.notifs.iter_mut().enumerate() {
|
||||||
|
match info.time {
|
||||||
|
NotifInfoTime::Pending => {
|
||||||
|
if self.light.is_none() {
|
||||||
|
let now = Instant::now();
|
||||||
|
info.time = NotifInfoTime::FadingIn(now);
|
||||||
|
if let Some(color) = info.color {
|
||||||
|
self.light = Some((now, color));
|
||||||
|
}
|
||||||
|
adjust_heights = true;
|
||||||
|
gui.inner.config_mut().enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NotifInfoTime::FadingIn(since) => {
|
||||||
|
adjust_heights = true;
|
||||||
|
let p = since.elapsed().as_secs_f32() / 0.25;
|
||||||
|
if p >= 1.0 {
|
||||||
|
info.time = NotifInfoTime::Displayed(Instant::now());
|
||||||
|
info.progress = 0.0;
|
||||||
|
} else {
|
||||||
|
info.progress = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NotifInfoTime::Displayed(since) => {
|
||||||
|
let p = since.elapsed().as_secs_f32() / info.duration.as_secs_f32();
|
||||||
|
if p >= 1.0 {
|
||||||
|
info.time = NotifInfoTime::FadingOut(Instant::now());
|
||||||
|
info.progress = 0.0;
|
||||||
|
} else {
|
||||||
|
info.progress = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NotifInfoTime::FadingOut(since) => {
|
||||||
|
adjust_heights = true;
|
||||||
|
let p = since.elapsed().as_secs_f32() / 0.25;
|
||||||
|
if p >= 1.0 {
|
||||||
|
remove.push(i);
|
||||||
|
} else {
|
||||||
|
info.progress = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for index in remove.into_iter().rev() {
|
||||||
|
self.notifs.remove(index);
|
||||||
|
}
|
||||||
|
if adjust_heights {
|
||||||
|
self.adjust_heights();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn adjust_heights(&mut self) {
|
||||||
|
let screen_size = self.config.pixel_pos.size();
|
||||||
|
let width = 0.3;
|
||||||
|
let left = 0.5 - (0.5 * width);
|
||||||
|
let right = 0.5 + (0.5 * width);
|
||||||
|
let height = 0.2 * width * screen_size.x / screen_size.y;
|
||||||
|
let space = 0.05 / 0.2 * height;
|
||||||
|
let mut y = 0.0;
|
||||||
|
for (gui, info) in self.notifs.iter_mut() {
|
||||||
|
y += space;
|
||||||
|
let pos_y = if matches!(info.time, NotifInfoTime::FadingOut(..)) {
|
||||||
|
let v = y - (height + y) * info.progress * info.progress;
|
||||||
|
// for notifs below this one
|
||||||
|
y -= (height + space) * crate::gui_screen::transition(info.progress);
|
||||||
|
v
|
||||||
|
} else if matches!(info.time, NotifInfoTime::FadingIn(..)) {
|
||||||
|
-height + (height + y) * (1.0 - (1.0 - info.progress) * (1.0 - info.progress))
|
||||||
|
} else {
|
||||||
|
y
|
||||||
|
};
|
||||||
|
y += height;
|
||||||
|
gui.inner.config_mut().pos =
|
||||||
|
Rectangle::from_tuples((left, pos_y), (right, pos_y + height));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct NotifInfo {
|
||||||
|
time: NotifInfoTime,
|
||||||
|
duration: Duration,
|
||||||
|
/// when the notification is first shown on screen,
|
||||||
|
/// light up the edges of the screen/window
|
||||||
|
/// in this color (usually red for important things)
|
||||||
|
color: Option<Color>,
|
||||||
|
/// used for fade-out animation
|
||||||
|
progress: f32,
|
||||||
|
}
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum NotifInfoTime {
|
||||||
|
Pending,
|
||||||
|
FadingIn(Instant),
|
||||||
|
Displayed(Instant),
|
||||||
|
FadingOut(Instant),
|
||||||
|
}
|
||||||
|
impl NotifInfo {
|
||||||
|
pub fn new(duration: Duration) -> Self {
|
||||||
|
Self {
|
||||||
|
time: NotifInfoTime::Pending,
|
||||||
|
duration,
|
||||||
|
color: None,
|
||||||
|
progress: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn with_highlight(mut self, color: Color) -> Self {
|
||||||
|
self.color = Some(color);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for NotifOverlay {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self::new().0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GuiElemTrait for NotifOverlay {
|
||||||
|
fn draw(&mut self, info: &mut crate::gui::DrawInfo, g: &mut speedy2d::Graphics2D) {
|
||||||
|
if let Ok(notif) = self.receiver.try_recv() {
|
||||||
|
let mut n = notif(self);
|
||||||
|
n.0.inner.config_mut().enabled = false;
|
||||||
|
self.notifs.push(n);
|
||||||
|
}
|
||||||
|
self.check_notifs();
|
||||||
|
// light
|
||||||
|
if let Some((since, color)) = self.light {
|
||||||
|
let p = since.elapsed().as_secs_f32() / 0.5;
|
||||||
|
if p >= 1.0 {
|
||||||
|
self.light = None;
|
||||||
|
} else {
|
||||||
|
let f = p * 2.0 - 1.0;
|
||||||
|
let f = 1.0 - f * f;
|
||||||
|
let color = Color::from_rgba(color.r(), color.g(), color.b(), color.a() * f);
|
||||||
|
let Vector2 { x: x1, y: y1 } = *info.pos.top_left();
|
||||||
|
let Vector2 { x: x2, y: y2 } = *info.pos.bottom_right();
|
||||||
|
let width = info.pos.width() * 0.01;
|
||||||
|
g.draw_rectangle(Rectangle::from_tuples((x1, y1), (x1 + width, y2)), color);
|
||||||
|
g.draw_rectangle(Rectangle::from_tuples((x2 - width, y1), (x2, y2)), color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// redraw
|
||||||
|
if !self.notifs.is_empty() {
|
||||||
|
if let Some(h) = &info.helper {
|
||||||
|
h.request_redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn draw_rev(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
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 GuiElem> + '_> {
|
||||||
|
Box::new(self.notifs.iter_mut().map(|(v, _)| v))
|
||||||
|
}
|
||||||
|
fn any(&self) -> &dyn std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
fn any_mut(&mut self) -> &mut dyn std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
fn clone_gui(&self) -> Box<dyn GuiElemTrait> {
|
||||||
|
Box::new(self.clone())
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ use crate::{
|
|||||||
gui::{morph_rect, DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiElemTrait},
|
gui::{morph_rect, DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiElemTrait},
|
||||||
gui_base::{Button, Panel},
|
gui_base::{Button, Panel},
|
||||||
gui_library::LibraryBrowser,
|
gui_library::LibraryBrowser,
|
||||||
|
gui_notif::NotifOverlay,
|
||||||
gui_playback::{CurrentSong, PlayPauseToggle},
|
gui_playback::{CurrentSong, PlayPauseToggle},
|
||||||
gui_queue::QueueViewer,
|
gui_queue::QueueViewer,
|
||||||
gui_settings::Settings,
|
gui_settings::Settings,
|
||||||
@ -34,15 +35,16 @@ pub fn transition(p: f32) -> f32 {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct GuiScreen {
|
pub struct GuiScreen {
|
||||||
config: GuiElemCfg,
|
config: GuiElemCfg,
|
||||||
/// 0: StatusBar / Idle display
|
/// 0: Notifications
|
||||||
/// 1: Settings
|
/// 1: StatusBar / Idle display
|
||||||
/// 2: Panel for Main view
|
/// 2: Settings
|
||||||
|
/// 3: Panel for Main view
|
||||||
/// 0: settings button
|
/// 0: settings button
|
||||||
/// 1: exit button
|
/// 1: exit button
|
||||||
/// 2: library browser
|
/// 2: library browser
|
||||||
/// 3: queue
|
/// 3: queue
|
||||||
/// 4: queue clear button
|
/// 4: queue clear button
|
||||||
/// 3: Edit Panel
|
/// 4: Edit Panel
|
||||||
children: Vec<GuiElem>,
|
children: Vec<GuiElem>,
|
||||||
pub idle: (bool, Option<Instant>),
|
pub idle: (bool, Option<Instant>),
|
||||||
pub settings: (bool, Option<Instant>),
|
pub settings: (bool, Option<Instant>),
|
||||||
@ -59,21 +61,22 @@ impl GuiScreen {
|
|||||||
} else {
|
} else {
|
||||||
edit.inner.config_mut().pos = Rectangle::from_tuples((0.0, 0.0), (0.5, 0.9));
|
edit.inner.config_mut().pos = Rectangle::from_tuples((0.0, 0.0), (0.5, 0.9));
|
||||||
}
|
}
|
||||||
if let Some(prev) = self.children.get_mut(3) {
|
if let Some(prev) = self.children.get_mut(4) {
|
||||||
prev.inner.config_mut().enabled = false;
|
prev.inner.config_mut().enabled = false;
|
||||||
}
|
}
|
||||||
self.children.insert(3, edit);
|
self.children.insert(4, edit);
|
||||||
}
|
}
|
||||||
pub fn close_edit(&mut self) {
|
pub fn close_edit(&mut self) {
|
||||||
if self.children.len() > 4 {
|
if self.children.len() > 5 {
|
||||||
self.children.remove(3);
|
self.children.remove(4);
|
||||||
self.children[3].inner.config_mut().enabled = true;
|
self.children[4].inner.config_mut().enabled = true;
|
||||||
} else if self.edit_panel.0 {
|
} else if self.edit_panel.0 {
|
||||||
self.edit_panel = (false, Some(Instant::now()));
|
self.edit_panel = (false, Some(Instant::now()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn new(
|
pub fn new(
|
||||||
config: GuiElemCfg,
|
config: GuiElemCfg,
|
||||||
|
notif_overlay: NotifOverlay,
|
||||||
line_height: f32,
|
line_height: f32,
|
||||||
scroll_sensitivity_pixels: f64,
|
scroll_sensitivity_pixels: f64,
|
||||||
scroll_sensitivity_lines: f64,
|
scroll_sensitivity_lines: f64,
|
||||||
@ -82,6 +85,7 @@ impl GuiScreen {
|
|||||||
Self {
|
Self {
|
||||||
config: config.w_keyboard_watch().w_mouse(),
|
config: config.w_keyboard_watch().w_mouse(),
|
||||||
children: vec![
|
children: vec![
|
||||||
|
GuiElem::new(notif_overlay),
|
||||||
GuiElem::new(StatusBar::new(
|
GuiElem::new(StatusBar::new(
|
||||||
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.9), (1.0, 1.0))),
|
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.9), (1.0, 1.0))),
|
||||||
true,
|
true,
|
||||||
@ -261,20 +265,20 @@ impl GuiElemTrait for GuiScreen {
|
|||||||
if let Some(h) = &info.helper {
|
if let Some(h) = &info.helper {
|
||||||
h.set_cursor_visible(!self.idle.0);
|
h.set_cursor_visible(!self.idle.0);
|
||||||
if self.settings.0 {
|
if self.settings.0 {
|
||||||
self.children[1].inner.config_mut().enabled = !self.idle.0;
|
self.children[2].inner.config_mut().enabled = !self.idle.0;
|
||||||
}
|
}
|
||||||
if self.edit_panel.0 {
|
if self.edit_panel.0 {
|
||||||
if let Some(c) = self.children.get_mut(3) {
|
if let Some(c) = self.children.get_mut(4) {
|
||||||
c.inner.config_mut().enabled = !self.idle.0;
|
c.inner.config_mut().enabled = !self.idle.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.children[2].inner.config_mut().enabled = !self.idle.0;
|
self.children[3].inner.config_mut().enabled = !self.idle.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let p = transition(p1);
|
let p = transition(p1);
|
||||||
self.children[0].inner.config_mut().pos =
|
self.children[1].inner.config_mut().pos =
|
||||||
Rectangle::from_tuples((0.0, 0.9 - 0.9 * p), (1.0, 1.0));
|
Rectangle::from_tuples((0.0, 0.9 - 0.9 * p), (1.0, 1.0));
|
||||||
self.children[0]
|
self.children[1]
|
||||||
.inner
|
.inner
|
||||||
.any_mut()
|
.any_mut()
|
||||||
.downcast_mut::<StatusBar>()
|
.downcast_mut::<StatusBar>()
|
||||||
@ -285,7 +289,7 @@ impl GuiElemTrait for GuiScreen {
|
|||||||
if self.settings.1.is_some() {
|
if self.settings.1.is_some() {
|
||||||
let p1 = Self::get_prog(&mut self.settings, 0.3);
|
let p1 = Self::get_prog(&mut self.settings, 0.3);
|
||||||
let p = transition(p1);
|
let p = transition(p1);
|
||||||
let cfg = self.children[1].inner.config_mut();
|
let cfg = self.children[2].inner.config_mut();
|
||||||
cfg.enabled = p > 0.0;
|
cfg.enabled = p > 0.0;
|
||||||
cfg.pos = Rectangle::from_tuples((0.0, 0.9 - 0.9 * p), (1.0, 0.9));
|
cfg.pos = Rectangle::from_tuples((0.0, 0.9 - 0.9 * p), (1.0, 0.9));
|
||||||
}
|
}
|
||||||
@ -293,22 +297,22 @@ impl GuiElemTrait for GuiScreen {
|
|||||||
if self.edit_panel.1.is_some() {
|
if self.edit_panel.1.is_some() {
|
||||||
let p1 = Self::get_prog(&mut self.edit_panel, 0.3);
|
let p1 = Self::get_prog(&mut self.edit_panel, 0.3);
|
||||||
let p = transition(p1);
|
let p = transition(p1);
|
||||||
if let Some(c) = self.children.get_mut(3) {
|
if let Some(c) = self.children.get_mut(4) {
|
||||||
c.inner.config_mut().enabled = p > 0.0;
|
c.inner.config_mut().enabled = p > 0.0;
|
||||||
c.inner.config_mut().pos =
|
c.inner.config_mut().pos =
|
||||||
Rectangle::from_tuples((-0.5 + 0.5 * p, 0.0), (0.5 * p, 0.9));
|
Rectangle::from_tuples((-0.5 + 0.5 * p, 0.0), (0.5 * p, 0.9));
|
||||||
}
|
}
|
||||||
if !self.edit_panel.0 && p == 0.0 {
|
if !self.edit_panel.0 && p == 0.0 {
|
||||||
while self.children.len() > 3 {
|
while self.children.len() > 4 {
|
||||||
self.children.pop();
|
self.children.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.children[2].inner.config_mut().pos =
|
self.children[3].inner.config_mut().pos =
|
||||||
Rectangle::from_tuples((0.5 * p, 0.0), (1.0 + 0.5 * p, 0.9));
|
Rectangle::from_tuples((0.5 * p, 0.0), (1.0 + 0.5 * p, 0.9));
|
||||||
}
|
}
|
||||||
// set idle timeout (only when settings are open)
|
// set idle timeout (only when settings are open)
|
||||||
if self.settings.0 || self.settings.1.is_some() {
|
if self.settings.0 || self.settings.1.is_some() {
|
||||||
self.idle_timeout = self.children[1]
|
self.idle_timeout = self.children[2]
|
||||||
.inner
|
.inner
|
||||||
.any()
|
.any()
|
||||||
.downcast_ref::<Settings>()
|
.downcast_ref::<Settings>()
|
||||||
|
@ -27,6 +27,8 @@ mod gui_edit;
|
|||||||
#[cfg(feature = "speedy2d")]
|
#[cfg(feature = "speedy2d")]
|
||||||
mod gui_library;
|
mod gui_library;
|
||||||
#[cfg(feature = "speedy2d")]
|
#[cfg(feature = "speedy2d")]
|
||||||
|
mod gui_notif;
|
||||||
|
#[cfg(feature = "speedy2d")]
|
||||||
mod gui_playback;
|
mod gui_playback;
|
||||||
#[cfg(feature = "speedy2d")]
|
#[cfg(feature = "speedy2d")]
|
||||||
mod gui_queue;
|
mod gui_queue;
|
||||||
|
@ -232,7 +232,14 @@ impl Database {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_command(&mut self, command: Command) {
|
pub fn apply_command(&mut self, mut command: Command) {
|
||||||
|
if !self.is_client() {
|
||||||
|
if let Command::ErrorInfo(t, _) = &mut command {
|
||||||
|
// clients can send ErrorInfo to the server and it will show up on other clients,
|
||||||
|
// BUT only the server can set the Title of the ErrorInfo.
|
||||||
|
t.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
// since db.update_endpoints is empty for clients, this won't cause unwanted back and forth
|
// since db.update_endpoints is empty for clients, this won't cause unwanted back and forth
|
||||||
self.broadcast_update(&command);
|
self.broadcast_update(&command);
|
||||||
match command {
|
match command {
|
||||||
@ -349,6 +356,7 @@ impl Database {
|
|||||||
Command::InitComplete => {
|
Command::InitComplete => {
|
||||||
self.client_is_init = true;
|
self.client_is_init = true;
|
||||||
}
|
}
|
||||||
|
Command::ErrorInfo(..) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,7 +154,7 @@ impl Song {
|
|||||||
{
|
{
|
||||||
Ok(data) => Some(data),
|
Ok(data) => Some(data),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("[info] error loading song {id}: {e}");
|
eprintln!("[WARN] error loading song {id}: {e}");
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,6 +139,13 @@ impl Player {
|
|||||||
db.apply_command(Command::NextSong);
|
db.apply_command(Command::NextSong);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// couldn't load song bytes
|
||||||
|
db.broadcast_update(&Command::ErrorInfo(
|
||||||
|
"NoSongData".to_owned(),
|
||||||
|
format!("Couldn't load song #{}\n({})", song.id, song.title),
|
||||||
|
));
|
||||||
|
db.apply_command(Command::NextSong);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.source = None;
|
self.source = None;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
pub mod get;
|
pub mod get;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
eprintln,
|
|
||||||
io::{BufRead, BufReader, Read, Write},
|
io::{BufRead, BufReader, Read, Write},
|
||||||
net::{SocketAddr, TcpListener},
|
net::{SocketAddr, TcpListener},
|
||||||
sync::{mpsc, Arc, Mutex},
|
sync::{mpsc, Arc, Mutex},
|
||||||
@ -51,6 +50,7 @@ pub enum Command {
|
|||||||
RemoveArtist(ArtistId),
|
RemoveArtist(ArtistId),
|
||||||
ModifyArtist(Artist),
|
ModifyArtist(Artist),
|
||||||
InitComplete,
|
InitComplete,
|
||||||
|
ErrorInfo(String, String),
|
||||||
}
|
}
|
||||||
impl Command {
|
impl Command {
|
||||||
pub fn send_to_server(self, db: &Database) -> Result<(), Self> {
|
pub fn send_to_server(self, db: &Database) -> Result<(), Self> {
|
||||||
@ -106,7 +106,6 @@ pub fn run_server(
|
|||||||
let command_sender = command_sender.clone();
|
let command_sender = command_sender.clone();
|
||||||
let db = Arc::clone(&db);
|
let db = Arc::clone(&db);
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
eprintln!("[info] TCP connection accepted from {con_addr}.");
|
|
||||||
// each connection first has to send one line to tell us what it wants
|
// each connection first has to send one line to tell us what it wants
|
||||||
let mut connection = BufReader::new(connection);
|
let mut connection = BufReader::new(connection);
|
||||||
let mut line = String::new();
|
let mut line = String::new();
|
||||||
@ -279,6 +278,11 @@ impl ToFromBytes for Command {
|
|||||||
Self::InitComplete => {
|
Self::InitComplete => {
|
||||||
s.write_all(&[0b00110001])?;
|
s.write_all(&[0b00110001])?;
|
||||||
}
|
}
|
||||||
|
Self::ErrorInfo(t, d) => {
|
||||||
|
s.write_all(&[0b11011011])?;
|
||||||
|
t.to_bytes(s)?;
|
||||||
|
d.to_bytes(s)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -324,6 +328,7 @@ impl ToFromBytes for Command {
|
|||||||
0b11011100 => Self::RemoveArtist(ToFromBytes::from_bytes(s)?),
|
0b11011100 => Self::RemoveArtist(ToFromBytes::from_bytes(s)?),
|
||||||
0b01011101 => Self::AddCover(ToFromBytes::from_bytes(s)?),
|
0b01011101 => Self::AddCover(ToFromBytes::from_bytes(s)?),
|
||||||
0b00110001 => Self::InitComplete,
|
0b00110001 => Self::InitComplete,
|
||||||
|
0b11011011 => Self::ErrorInfo(ToFromBytes::from_bytes(s)?, ToFromBytes::from_bytes(s)?),
|
||||||
_ => {
|
_ => {
|
||||||
eprintln!("unexpected byte when reading command; stopping playback.");
|
eprintln!("unexpected byte when reading command; stopping playback.");
|
||||||
Self::Stop
|
Self::Stop
|
||||||
|
@ -438,7 +438,9 @@ async fn sse_handler(
|
|||||||
.collect::<String>(),
|
.collect::<String>(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Command::Save | Command::InitComplete => return Poll::Pending,
|
Command::Save | Command::InitComplete | Command::ErrorInfo(..) => {
|
||||||
|
return Poll::Pending
|
||||||
|
}
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
return Poll::Pending;
|
return Poll::Pending;
|
||||||
|
Loading…
Reference in New Issue
Block a user