mirror of
https://github.com/Dummi26/musicdb.git
synced 2025-03-10 05:43:53 +01:00
aaaaa
This commit is contained in:
parent
85c79a21e1
commit
d3a1facba0
@ -12,7 +12,7 @@ directories = "5.0.1"
|
|||||||
regex = "1.9.3"
|
regex = "1.9.3"
|
||||||
speedy2d = { version = "1.12.0", optional = true }
|
speedy2d = { version = "1.12.0", optional = true }
|
||||||
toml = "0.7.6"
|
toml = "0.7.6"
|
||||||
musicdb-mers = { version = "0.1.0", path = "../musicdb-mers", optional = true }
|
# musicdb-mers = { version = "0.1.0", path = "../musicdb-mers", optional = true }
|
||||||
uianimator = "0.1.1"
|
uianimator = "0.1.1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
@ -26,8 +26,8 @@ default = ["gui", "default-playback"]
|
|||||||
# playback:
|
# playback:
|
||||||
# enables syncplayer modes, where the client mirrors the server's playback
|
# enables syncplayer modes, where the client mirrors the server's playback
|
||||||
gui = ["speedy2d"]
|
gui = ["speedy2d"]
|
||||||
merscfg = ["mers", "gui"]
|
# merscfg = ["mers", "gui"]
|
||||||
mers = ["musicdb-mers"]
|
# mers = ["musicdb-mers"]
|
||||||
playback = []
|
playback = []
|
||||||
default-playback = ["playback", "musicdb-lib/default-playback"]
|
default-playback = ["playback", "musicdb-lib/default-playback"]
|
||||||
playback-via-playback-rs = ["playback", "musicdb-lib/playback-via-playback-rs"]
|
playback-via-playback-rs = ["playback", "musicdb-lib/playback-via-playback-rs"]
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
|
# [build]
|
||||||
|
# pre-build = [
|
||||||
|
# "dpkg --add-architecture $CROSS_DEB_ARCH",
|
||||||
|
# "apt-get update && apt-get --assume-yes install libasound2-dev libasound2-dev:$CROSS_DEB_ARCH"
|
||||||
|
# ]
|
||||||
|
# default-target = "aarch64-unknown-linux-gnu"
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
pre-build = [
|
pre-build = [
|
||||||
"dpkg --add-architecture $CROSS_DEB_ARCH",
|
"dpkg --add-architecture $CROSS_DEB_ARCH",
|
||||||
"apt-get update && apt-get --assume-yes install libasound2-dev libasound2-dev:$CROSS_DEB_ARCH"
|
"apt-get update && apt-get --assume-yes install libasound2-dev"
|
||||||
]
|
]
|
||||||
default-target = "aarch64-unknown-linux-gnu"
|
default-target = "x86_64-unknown-linux-musl"
|
||||||
|
@ -362,90 +362,124 @@ impl Gui {
|
|||||||
}
|
}
|
||||||
Ok(Ok(Ok(()))) => eprintln!("Info: using merscfg"),
|
Ok(Ok(Ok(()))) => eprintln!("Info: using merscfg"),
|
||||||
}
|
}
|
||||||
database.lock().unwrap().update_endpoints.push(
|
{
|
||||||
musicdb_lib::data::database::UpdateEndpoint::Custom(Box::new(move |cmd| {
|
let mut db = database.lock().unwrap();
|
||||||
match &cmd.action {
|
let udepid = db.update_endpoints_id;
|
||||||
Action::Resume
|
db.update_endpoints_id += 1;
|
||||||
| Action::Pause
|
db.update_endpoints.push((
|
||||||
| Action::Stop
|
udepid,
|
||||||
| Action::Save
|
musicdb_lib::data::database::UpdateEndpoint::Custom(Box::new(
|
||||||
| Action::InitComplete => {}
|
move |cmd| match &cmd.action {
|
||||||
Action::NextSong
|
Action::Resume
|
||||||
| Action::QueueUpdate(..)
|
| Action::Pause
|
||||||
| Action::QueueAdd(..)
|
| Action::Stop
|
||||||
| Action::QueueInsert(..)
|
| Action::Save
|
||||||
| Action::QueueRemove(..)
|
| Action::InitComplete => {}
|
||||||
| Action::QueueMove(..)
|
Action::NextSong
|
||||||
| Action::QueueMoveInto(..)
|
| Action::QueueUpdate(..)
|
||||||
| Action::QueueGoto(..)
|
| Action::QueueAdd(..)
|
||||||
| Action::QueueShuffle(..)
|
| Action::QueueInsert(..)
|
||||||
| Action::QueueSetShuffle(..)
|
| Action::QueueRemove(..)
|
||||||
| Action::QueueUnshuffle(..) => {
|
| Action::QueueMove(..)
|
||||||
if let Some(s) = &*event_sender_arc.lock().unwrap() {
|
| Action::QueueMoveInto(..)
|
||||||
_ = s.send_event(GuiEvent::UpdatedQueue);
|
| Action::QueueGoto(..)
|
||||||
|
| Action::QueueShuffle(..)
|
||||||
|
| Action::QueueSetShuffle(..)
|
||||||
|
| Action::QueueUnshuffle(..) => {
|
||||||
|
if let Some(s) = &*event_sender_arc.lock().unwrap() {
|
||||||
|
_ = s.send_event(GuiEvent::UpdatedQueue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
Action::SyncDatabase(..)
|
||||||
Action::SyncDatabase(..)
|
| Action::AddSong(_, _)
|
||||||
| Action::AddSong(_)
|
| Action::AddAlbum(_, _)
|
||||||
| Action::AddAlbum(_)
|
| Action::AddArtist(_, _)
|
||||||
| Action::AddArtist(_)
|
| Action::AddCover(_, _)
|
||||||
| Action::AddCover(_)
|
| Action::ModifySong(_, _)
|
||||||
| Action::ModifySong(_)
|
| Action::ModifyAlbum(_, _)
|
||||||
| Action::ModifyAlbum(_)
|
| Action::ModifyArtist(_, _)
|
||||||
| Action::ModifyArtist(_)
|
| Action::RemoveSong(_)
|
||||||
| Action::RemoveSong(_)
|
| Action::RemoveAlbum(_)
|
||||||
| Action::RemoveAlbum(_)
|
| Action::RemoveArtist(_)
|
||||||
| Action::RemoveArtist(_)
|
| Action::TagSongFlagSet(..)
|
||||||
| Action::TagSongFlagSet(..)
|
| Action::TagSongFlagUnset(..)
|
||||||
| Action::TagSongFlagUnset(..)
|
| Action::TagAlbumFlagSet(..)
|
||||||
| Action::TagAlbumFlagSet(..)
|
| Action::TagAlbumFlagUnset(..)
|
||||||
| Action::TagAlbumFlagUnset(..)
|
| Action::TagArtistFlagSet(..)
|
||||||
| Action::TagArtistFlagSet(..)
|
| Action::TagArtistFlagUnset(..)
|
||||||
| Action::TagArtistFlagUnset(..)
|
| Action::TagSongPropertySet(..)
|
||||||
| Action::TagSongPropertySet(..)
|
| Action::TagSongPropertyUnset(..)
|
||||||
| Action::TagSongPropertyUnset(..)
|
| Action::TagAlbumPropertySet(..)
|
||||||
| Action::TagAlbumPropertySet(..)
|
| Action::TagAlbumPropertyUnset(..)
|
||||||
| Action::TagAlbumPropertyUnset(..)
|
| Action::TagArtistPropertySet(..)
|
||||||
| Action::TagArtistPropertySet(..)
|
| Action::TagArtistPropertyUnset(..)
|
||||||
| Action::TagArtistPropertyUnset(..)
|
| Action::SetSongDuration(..) => {
|
||||||
| Action::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);
|
}
|
||||||
}
|
}
|
||||||
}
|
Action::ErrorInfo(t, d) => {
|
||||||
Action::ErrorInfo(t, d) => {
|
let (t, d) = (t.clone(), d.clone());
|
||||||
let (t, d) = (t.clone(), d.clone());
|
notif_sender_two
|
||||||
notif_sender_two
|
.send(Box::new(move |_| {
|
||||||
.send(Box::new(move |_| {
|
(
|
||||||
(
|
Box::new(Panel::with_background(
|
||||||
Box::new(Panel::with_background(
|
|
||||||
GuiElemCfg::default(),
|
|
||||||
[Label::new(
|
|
||||||
GuiElemCfg::default(),
|
GuiElemCfg::default(),
|
||||||
if t.is_empty() {
|
[Label::new(
|
||||||
format!("Server message\n{d}")
|
GuiElemCfg::default(),
|
||||||
} else {
|
if t.is_empty() {
|
||||||
format!("Server error ({t})\n{d}")
|
format!("Server message\n{d}")
|
||||||
},
|
} else {
|
||||||
Color::WHITE,
|
format!("Server error ({t})\n{d}")
|
||||||
None,
|
},
|
||||||
Vec2::new(0.5, 0.5),
|
Color::WHITE,
|
||||||
)],
|
None,
|
||||||
Color::from_rgba(0.0, 0.0, 0.0, 0.8),
|
Vec2::new(0.5, 0.5),
|
||||||
)),
|
)],
|
||||||
if t.is_empty() {
|
Color::from_rgba(0.0, 0.0, 0.0, 0.8),
|
||||||
NotifInfo::new(Duration::from_secs(2))
|
)),
|
||||||
} else {
|
if t.is_empty() {
|
||||||
NotifInfo::new(Duration::from_secs(5))
|
NotifInfo::new(Duration::from_secs(2))
|
||||||
.with_highlight(Color::RED)
|
} else {
|
||||||
},
|
NotifInfo::new(Duration::from_secs(5))
|
||||||
)
|
.with_highlight(Color::RED)
|
||||||
}))
|
},
|
||||||
.unwrap();
|
)
|
||||||
}
|
}))
|
||||||
}
|
.unwrap();
|
||||||
})),
|
}
|
||||||
);
|
Action::Denied(req) => {
|
||||||
|
let req = *req;
|
||||||
|
notif_sender_two
|
||||||
|
.send(Box::new(move |_| {
|
||||||
|
(
|
||||||
|
Box::new(Panel::with_background(
|
||||||
|
GuiElemCfg::default(),
|
||||||
|
[Label::new(
|
||||||
|
GuiElemCfg::default(),
|
||||||
|
format!(
|
||||||
|
"server denied {}",
|
||||||
|
if req.is_some() {
|
||||||
|
"request, maybe desynced"
|
||||||
|
} else {
|
||||||
|
"action, likely desynced"
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Color::WHITE,
|
||||||
|
None,
|
||||||
|
Vec2::new(0.5, 0.5),
|
||||||
|
)],
|
||||||
|
Color::from_rgba(0.0, 0.0, 0.0, 0.8),
|
||||||
|
)),
|
||||||
|
NotifInfo::new(Duration::from_secs(1)),
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
));
|
||||||
|
}
|
||||||
let no_animations = false;
|
let no_animations = false;
|
||||||
Gui {
|
Gui {
|
||||||
event_sender,
|
event_sender,
|
||||||
@ -1307,16 +1341,10 @@ impl Gui {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
GuiAction::SendToServer(action) => {
|
GuiAction::SendToServer(action) => {
|
||||||
|
let command = self.database.lock().unwrap().seq.pack(action);
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
eprintln!("[DEBUG] Sending command to server: {action:?}");
|
eprintln!("[DEBUG] Sending command to server: {command:?}");
|
||||||
if let Err(e) = self
|
if let Err(e) = command.to_bytes(&mut self.connection) {
|
||||||
.database
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.seq
|
|
||||||
.pack(action)
|
|
||||||
.to_bytes(&mut self.connection)
|
|
||||||
{
|
|
||||||
eprintln!("Error sending command to server: {e}");
|
eprintln!("Error sending command to server: {e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ use std::time::Instant;
|
|||||||
|
|
||||||
use musicdb_lib::{
|
use musicdb_lib::{
|
||||||
data::{song::Song, ArtistId},
|
data::{song::Song, ArtistId},
|
||||||
server::Action,
|
server::{Action, Req},
|
||||||
};
|
};
|
||||||
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle};
|
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle};
|
||||||
|
|
||||||
@ -189,7 +189,10 @@ impl GuiElem for EditorForSongs {
|
|||||||
song.album = None;
|
song.album = None;
|
||||||
}
|
}
|
||||||
info.actions
|
info.actions
|
||||||
.push(GuiAction::SendToServer(Action::ModifySong(song)));
|
.push(GuiAction::SendToServer(Action::ModifySong(
|
||||||
|
song,
|
||||||
|
Req::none(),
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::SetArtist(name, id) => {
|
Event::SetArtist(name, id) => {
|
||||||
|
@ -5,7 +5,7 @@ use musicdb_lib::{
|
|||||||
song::Song,
|
song::Song,
|
||||||
AlbumId, ArtistId,
|
AlbumId, ArtistId,
|
||||||
},
|
},
|
||||||
server::Action,
|
server::{Action, Req},
|
||||||
};
|
};
|
||||||
use speedy2d::{
|
use speedy2d::{
|
||||||
color::Color,
|
color::Color,
|
||||||
@ -404,7 +404,7 @@ impl GuiElem for QueueEmptySpaceDragHandler {
|
|||||||
dragged_add_to_queue(
|
dragged_add_to_queue(
|
||||||
dragged,
|
dragged,
|
||||||
(),
|
(),
|
||||||
|_, q| Action::QueueAdd(vec![], q),
|
|_, q| Action::QueueAdd(vec![], q, Req::none()),
|
||||||
|_, q| Action::QueueMoveInto(q, vec![]),
|
|_, q| Action::QueueMoveInto(q, vec![]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -631,9 +631,9 @@ impl GuiElem for QueueSong {
|
|||||||
self.path.clone(),
|
self.path.clone(),
|
||||||
move |mut p: Vec<usize>, q| {
|
move |mut p: Vec<usize>, q| {
|
||||||
if let Some(j) = p.pop() {
|
if let Some(j) = p.pop() {
|
||||||
Action::QueueInsert(p, if insert_below { j + 1 } else { j }, q)
|
Action::QueueInsert(p, if insert_below { j + 1 } else { j }, q, Req::none())
|
||||||
} else {
|
} else {
|
||||||
Action::QueueAdd(p, q)
|
Action::QueueAdd(p, q, Req::none())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
move |mut p, q| {
|
move |mut p, q| {
|
||||||
@ -826,7 +826,7 @@ impl GuiElem for QueueFolder {
|
|||||||
dragged_add_to_queue(
|
dragged_add_to_queue(
|
||||||
dragged,
|
dragged,
|
||||||
self.path.clone(),
|
self.path.clone(),
|
||||||
|p, q| Action::QueueAdd(p, q),
|
|p, q| Action::QueueAdd(p, q, Req::none()),
|
||||||
|p, q| Action::QueueMoveInto(q, p),
|
|p, q| Action::QueueMoveInto(q, p),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@ -835,7 +835,7 @@ impl GuiElem for QueueFolder {
|
|||||||
self.path.clone(),
|
self.path.clone(),
|
||||||
|mut p, q| {
|
|mut p, q| {
|
||||||
let j = p.pop().unwrap_or(0);
|
let j = p.pop().unwrap_or(0);
|
||||||
Action::QueueInsert(p, j, q)
|
Action::QueueInsert(p, j, q, Req::none())
|
||||||
},
|
},
|
||||||
|p, q| Action::QueueMove(q, p),
|
|p, q| Action::QueueMove(q, p),
|
||||||
)
|
)
|
||||||
@ -903,7 +903,7 @@ impl GuiElem for QueueIndentEnd {
|
|||||||
dragged_add_to_queue(
|
dragged_add_to_queue(
|
||||||
dragged,
|
dragged,
|
||||||
self.path_insert.clone(),
|
self.path_insert.clone(),
|
||||||
|(p, j), q| Action::QueueInsert(p, j, q),
|
|(p, j), q| Action::QueueInsert(p, j, q, Req::none()),
|
||||||
|(mut p, j), q| {
|
|(mut p, j), q| {
|
||||||
p.push(j);
|
p.push(j);
|
||||||
Action::QueueMove(q, p)
|
Action::QueueMove(q, p)
|
||||||
@ -1066,7 +1066,7 @@ impl GuiElem for QueueLoop {
|
|||||||
dragged_add_to_queue(
|
dragged_add_to_queue(
|
||||||
dragged,
|
dragged,
|
||||||
p,
|
p,
|
||||||
|p, q| Action::QueueAdd(p, q),
|
|p, q| Action::QueueAdd(p, q, Req::none()),
|
||||||
|p, q| Action::QueueMoveInto(q, p),
|
|p, q| Action::QueueMoveInto(q, p),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
@ -2,7 +2,7 @@ use std::time::Instant;
|
|||||||
|
|
||||||
use musicdb_lib::{
|
use musicdb_lib::{
|
||||||
data::queue::{QueueContent, QueueFolder},
|
data::queue::{QueueContent, QueueFolder},
|
||||||
server::Action,
|
server::{Action, Req},
|
||||||
};
|
};
|
||||||
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, window::VirtualKeyCode, Graphics2D};
|
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, window::VirtualKeyCode, Graphics2D};
|
||||||
use uianimator::{default_animator_f64_quadratic::DefaultAnimatorF64Quadratic, Animator};
|
use uianimator::{default_animator_f64_quadratic::DefaultAnimatorF64Quadratic, Animator};
|
||||||
@ -126,6 +126,7 @@ impl GuiScreen {
|
|||||||
musicdb_lib::data::queue::QueueFolder::default(),
|
musicdb_lib::data::queue::QueueFolder::default(),
|
||||||
)
|
)
|
||||||
.into(),
|
.into(),
|
||||||
|
Req::none(),
|
||||||
))]
|
))]
|
||||||
},
|
},
|
||||||
[Label::new(
|
[Label::new(
|
||||||
|
@ -203,7 +203,7 @@ fn main() {
|
|||||||
action(action);
|
action(action);
|
||||||
break 'feature_if;
|
break 'feature_if;
|
||||||
}
|
}
|
||||||
db.apply_action_unchecked_seq(action);
|
db.apply_action_unchecked_seq(action, None);
|
||||||
}
|
}
|
||||||
#[cfg(feature = "playback")]
|
#[cfg(feature = "playback")]
|
||||||
if let Some(player) = &mut player {
|
if let Some(player) = &mut player {
|
||||||
|
@ -4,7 +4,7 @@ use crate::load::ToFromBytes;
|
|||||||
|
|
||||||
use super::{AlbumId, ArtistId, CoverId, GeneralData, SongId};
|
use super::{AlbumId, ArtistId, CoverId, GeneralData, SongId};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct Album {
|
pub struct Album {
|
||||||
pub id: AlbumId,
|
pub id: AlbumId,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -4,7 +4,7 @@ use crate::load::ToFromBytes;
|
|||||||
|
|
||||||
use super::{AlbumId, ArtistId, CoverId, GeneralData, SongId};
|
use super::{AlbumId, ArtistId, CoverId, GeneralData, SongId};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct Artist {
|
pub struct Artist {
|
||||||
pub id: ArtistId,
|
pub id: ArtistId,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -13,7 +13,7 @@ use rand::thread_rng;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
load::ToFromBytes,
|
load::ToFromBytes,
|
||||||
server::{Action, Command, Commander},
|
server::{Action, Command, Commander, Req},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@ -45,10 +45,11 @@ pub struct Database {
|
|||||||
pub queue: Queue,
|
pub queue: Queue,
|
||||||
/// if the database receives an update, it will inform all of its clients so they can stay in sync.
|
/// if the database receives an update, it will inform all of its clients so they can stay in sync.
|
||||||
/// this is a list containing all the clients.
|
/// this is a list containing all the clients.
|
||||||
pub update_endpoints: Vec<UpdateEndpoint>,
|
pub update_endpoints: Vec<(u64, UpdateEndpoint)>,
|
||||||
|
pub update_endpoints_id: u64,
|
||||||
/// true if a song is/should be playing
|
/// true if a song is/should be playing
|
||||||
pub playing: bool,
|
pub playing: bool,
|
||||||
pub command_sender: Option<mpsc::Sender<Command>>,
|
pub command_sender: Option<mpsc::Sender<(Command, Option<u64>)>>,
|
||||||
pub remote_server_as_song_file_source:
|
pub remote_server_as_song_file_source:
|
||||||
Option<Arc<Mutex<crate::server::get::Client<Box<dyn ClientIo>>>>>,
|
Option<Arc<Mutex<crate::server::get::Client<Box<dyn ClientIo>>>>>,
|
||||||
/// only relevant for clients. true if init is done
|
/// only relevant for clients. true if init is done
|
||||||
@ -65,7 +66,7 @@ pub enum UpdateEndpoint {
|
|||||||
Bytes(Box<dyn Write + Sync + Send>),
|
Bytes(Box<dyn Write + Sync + Send>),
|
||||||
CmdChannel(mpsc::Sender<Arc<Command>>),
|
CmdChannel(mpsc::Sender<Arc<Command>>),
|
||||||
Custom(Box<dyn FnMut(&Command) + Send>),
|
Custom(Box<dyn FnMut(&Command) + Send>),
|
||||||
CustomArc(Box<dyn FnMut(&Arc<Command>) + Send>),
|
CustomArc(Box<dyn FnMut(Arc<Command>) + Send>),
|
||||||
CustomBytes(Box<dyn FnMut(&[u8]) + Send>),
|
CustomBytes(Box<dyn FnMut(&[u8]) + Send>),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -507,7 +508,7 @@ impl Database {
|
|||||||
))
|
))
|
||||||
.to_bytes(con)?;
|
.to_bytes(con)?;
|
||||||
self.seq
|
self.seq
|
||||||
.pack(Action::QueueUpdate(vec![], self.queue.clone()))
|
.pack(Action::QueueUpdate(vec![], self.queue.clone(), Req::none()))
|
||||||
.to_bytes(con)?;
|
.to_bytes(con)?;
|
||||||
if self.playing {
|
if self.playing {
|
||||||
self.seq.pack(Action::Resume).to_bytes(con)?;
|
self.seq.pack(Action::Resume).to_bytes(con)?;
|
||||||
@ -521,8 +522,29 @@ impl Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `apply_action_unchecked_seq(command.action)` if `command.seq` is correct or `0xFF`
|
/// `apply_action_unchecked_seq(command.action)` if `command.seq` is correct or `0xFF`
|
||||||
pub fn apply_command(&mut self, command: Command) {
|
pub fn apply_command(&mut self, mut command: Command, client: Option<u64>) {
|
||||||
if command.seq != self.seq.seq() && command.seq != 0xFF {
|
if command.seq != self.seq.seq() && command.seq != 0xFF {
|
||||||
|
if let Some(client) = client {
|
||||||
|
for (udepid, udep) in &mut self.update_endpoints {
|
||||||
|
if client == *udepid {
|
||||||
|
let denied =
|
||||||
|
Action::Denied(command.action.get_req().unwrap_or_else(Req::none))
|
||||||
|
.cmd(0xFFu8);
|
||||||
|
match udep {
|
||||||
|
UpdateEndpoint::Bytes(w) => {
|
||||||
|
let _ = w.write(&denied.to_bytes_vec());
|
||||||
|
}
|
||||||
|
UpdateEndpoint::CmdChannel(w) => {
|
||||||
|
let _ = w.send(Arc::new(denied));
|
||||||
|
}
|
||||||
|
UpdateEndpoint::Custom(w) => w(&denied),
|
||||||
|
UpdateEndpoint::CustomArc(w) => w(Arc::new(denied)),
|
||||||
|
UpdateEndpoint::CustomBytes(w) => w(&denied.to_bytes_vec()),
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"Invalid sequence number: got {} but expected {}.",
|
"Invalid sequence number: got {} but expected {}.",
|
||||||
command.seq,
|
command.seq,
|
||||||
@ -530,9 +552,9 @@ impl Database {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.apply_action_unchecked_seq(command.action)
|
self.apply_action_unchecked_seq(command.action, client)
|
||||||
}
|
}
|
||||||
pub fn apply_action_unchecked_seq(&mut self, mut action: Action) {
|
pub fn apply_action_unchecked_seq(&mut self, mut action: Action, client: Option<u64>) {
|
||||||
if !self.is_client() {
|
if !self.is_client() {
|
||||||
if let Action::ErrorInfo(t, _) = &mut action {
|
if let Action::ErrorInfo(t, _) = &mut action {
|
||||||
// clients can send ErrorInfo to the server and it will show up on other clients,
|
// clients can send ErrorInfo to the server and it will show up on other clients,
|
||||||
@ -548,7 +570,7 @@ impl Database {
|
|||||||
Action::Pause if !self.playing => (),
|
Action::Pause if !self.playing => (),
|
||||||
Action::Resume if self.playing => (),
|
Action::Resume if self.playing => (),
|
||||||
// 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
|
||||||
_ => action = self.broadcast_update(action),
|
_ => action = self.broadcast_update(action, client),
|
||||||
}
|
}
|
||||||
match action {
|
match action {
|
||||||
Action::Resume => self.playing = true,
|
Action::Resume => self.playing = true,
|
||||||
@ -557,7 +579,7 @@ impl Database {
|
|||||||
Action::NextSong => {
|
Action::NextSong => {
|
||||||
if !Queue::advance_index_db(self) {
|
if !Queue::advance_index_db(self) {
|
||||||
// end of queue
|
// end of queue
|
||||||
self.apply_action_unchecked_seq(Action::Pause);
|
self.apply_action_unchecked_seq(Action::Pause, client);
|
||||||
self.queue.init();
|
self.queue.init();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -567,17 +589,17 @@ impl Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Action::SyncDatabase(a, b, c) => self.sync(a, b, c),
|
Action::SyncDatabase(a, b, c) => self.sync(a, b, c),
|
||||||
Action::QueueUpdate(index, new_data) => {
|
Action::QueueUpdate(index, new_data, _) => {
|
||||||
if let Some(v) = self.queue.get_item_at_index_mut(&index, 0) {
|
if let Some(v) = self.queue.get_item_at_index_mut(&index, 0) {
|
||||||
*v = new_data;
|
*v = new_data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Action::QueueAdd(index, new_data) => {
|
Action::QueueAdd(index, new_data, _) => {
|
||||||
if let Some(v) = self.queue.get_item_at_index_mut(&index, 0) {
|
if let Some(v) = self.queue.get_item_at_index_mut(&index, 0) {
|
||||||
v.add_to_end(new_data, false);
|
v.add_to_end(new_data, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Action::QueueInsert(index, pos, new_data) => {
|
Action::QueueInsert(index, pos, new_data, _) => {
|
||||||
if let Some(v) = self.queue.get_item_at_index_mut(&index, 0) {
|
if let Some(v) = self.queue.get_item_at_index_mut(&index, 0) {
|
||||||
v.insert(new_data, pos, false);
|
v.insert(new_data, pos, false);
|
||||||
}
|
}
|
||||||
@ -662,7 +684,7 @@ impl Database {
|
|||||||
{
|
{
|
||||||
let mut ord: Vec<usize> = (0..content.len()).collect();
|
let mut ord: Vec<usize> = (0..content.len()).collect();
|
||||||
ord.shuffle(&mut thread_rng());
|
ord.shuffle(&mut thread_rng());
|
||||||
self.apply_action_unchecked_seq(Action::QueueSetShuffle(path, ord));
|
self.apply_action_unchecked_seq(Action::QueueSetShuffle(path, ord), client);
|
||||||
} else {
|
} else {
|
||||||
eprintln!("(QueueShuffle) QueueElement at {path:?} not a folder!");
|
eprintln!("(QueueShuffle) QueueElement at {path:?} not a folder!");
|
||||||
}
|
}
|
||||||
@ -719,23 +741,23 @@ impl Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Action::AddSong(song) => {
|
Action::AddSong(song, _) => {
|
||||||
self.add_song_new(song);
|
self.add_song_new(song);
|
||||||
}
|
}
|
||||||
Action::AddAlbum(album) => {
|
Action::AddAlbum(album, _) => {
|
||||||
self.add_album_new(album);
|
self.add_album_new(album);
|
||||||
}
|
}
|
||||||
Action::AddArtist(artist) => {
|
Action::AddArtist(artist, _) => {
|
||||||
self.add_artist_new(artist);
|
self.add_artist_new(artist);
|
||||||
}
|
}
|
||||||
Action::AddCover(cover) => _ = self.add_cover_new(cover),
|
Action::AddCover(cover, _) => _ = self.add_cover_new(cover),
|
||||||
Action::ModifySong(song) => {
|
Action::ModifySong(song, _) => {
|
||||||
_ = self.update_song(song);
|
_ = self.update_song(song);
|
||||||
}
|
}
|
||||||
Action::ModifyAlbum(album) => {
|
Action::ModifyAlbum(album, _) => {
|
||||||
_ = self.update_album(album);
|
_ = self.update_album(album);
|
||||||
}
|
}
|
||||||
Action::ModifyArtist(artist) => {
|
Action::ModifyArtist(artist, _) => {
|
||||||
_ = self.update_artist(artist);
|
_ = self.update_artist(artist);
|
||||||
}
|
}
|
||||||
Action::RemoveSong(song) => {
|
Action::RemoveSong(song) => {
|
||||||
@ -846,6 +868,7 @@ impl Database {
|
|||||||
self.client_is_init = true;
|
self.client_is_init = true;
|
||||||
}
|
}
|
||||||
Action::ErrorInfo(..) => {}
|
Action::ErrorInfo(..) => {}
|
||||||
|
Action::Denied(..) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -875,6 +898,7 @@ impl Database {
|
|||||||
custom_files: None,
|
custom_files: None,
|
||||||
queue: QueueContent::Folder(QueueFolder::default()).into(),
|
queue: QueueContent::Folder(QueueFolder::default()).into(),
|
||||||
update_endpoints: vec![],
|
update_endpoints: vec![],
|
||||||
|
update_endpoints_id: 0,
|
||||||
playing: false,
|
playing: false,
|
||||||
command_sender: None,
|
command_sender: None,
|
||||||
remote_server_as_song_file_source: None,
|
remote_server_as_song_file_source: None,
|
||||||
@ -896,6 +920,7 @@ impl Database {
|
|||||||
custom_files: None,
|
custom_files: None,
|
||||||
queue: QueueContent::Folder(QueueFolder::default()).into(),
|
queue: QueueContent::Folder(QueueFolder::default()).into(),
|
||||||
update_endpoints: vec![],
|
update_endpoints: vec![],
|
||||||
|
update_endpoints_id: 0,
|
||||||
playing: false,
|
playing: false,
|
||||||
command_sender: None,
|
command_sender: None,
|
||||||
remote_server_as_song_file_source: None,
|
remote_server_as_song_file_source: None,
|
||||||
@ -922,6 +947,7 @@ impl Database {
|
|||||||
custom_files: None,
|
custom_files: None,
|
||||||
queue: QueueContent::Folder(QueueFolder::default()).into(),
|
queue: QueueContent::Folder(QueueFolder::default()).into(),
|
||||||
update_endpoints: vec![],
|
update_endpoints: vec![],
|
||||||
|
update_endpoints_id: 0,
|
||||||
playing: false,
|
playing: false,
|
||||||
command_sender: None,
|
command_sender: None,
|
||||||
remote_server_as_song_file_source: None,
|
remote_server_as_song_file_source: None,
|
||||||
@ -973,7 +999,7 @@ impl Database {
|
|||||||
self.times_data_modified = None;
|
self.times_data_modified = None;
|
||||||
Ok(path)
|
Ok(path)
|
||||||
}
|
}
|
||||||
pub fn broadcast_update(&mut self, update: Action) -> Action {
|
pub fn broadcast_update(&mut self, update: Action, client: Option<u64>) -> Action {
|
||||||
match update {
|
match update {
|
||||||
Action::InitComplete => return update,
|
Action::InitComplete => return update,
|
||||||
_ => {}
|
_ => {}
|
||||||
@ -981,11 +1007,36 @@ impl Database {
|
|||||||
if !self.is_client() {
|
if !self.is_client() {
|
||||||
self.seq.inc();
|
self.seq.inc();
|
||||||
}
|
}
|
||||||
let update = self.seq.pack(update);
|
let mut update = self.seq.pack(update);
|
||||||
|
let req = update.action.take_req();
|
||||||
let mut remove = vec![];
|
let mut remove = vec![];
|
||||||
let mut bytes = None;
|
let mut bytes = None;
|
||||||
let mut arc = None;
|
let mut arc = None;
|
||||||
for (i, udep) in self.update_endpoints.iter_mut().enumerate() {
|
for (i, (udepid, udep)) in self.update_endpoints.iter_mut().enumerate() {
|
||||||
|
if req.is_some_and(|r| r.is_some()) && client.is_some_and(|v| *udepid == v) {
|
||||||
|
update.action.put_req(req.unwrap());
|
||||||
|
match udep {
|
||||||
|
UpdateEndpoint::Bytes(writer) => {
|
||||||
|
if writer.write_all(&update.to_bytes_vec()).is_err() {
|
||||||
|
remove.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UpdateEndpoint::CmdChannel(sender) => {
|
||||||
|
if sender.send(Arc::new(update.clone())).is_err() {
|
||||||
|
remove.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UpdateEndpoint::Custom(func) => func(&update),
|
||||||
|
UpdateEndpoint::CustomArc(func) => func(Arc::new(update.clone())),
|
||||||
|
UpdateEndpoint::CustomBytes(func) => {
|
||||||
|
if bytes.is_none() {
|
||||||
|
bytes = Some(update.to_bytes_vec());
|
||||||
|
}
|
||||||
|
func(bytes.as_ref().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
update.action.take_req();
|
||||||
|
}
|
||||||
match udep {
|
match udep {
|
||||||
UpdateEndpoint::Bytes(writer) => {
|
UpdateEndpoint::Bytes(writer) => {
|
||||||
if bytes.is_none() {
|
if bytes.is_none() {
|
||||||
@ -1008,7 +1059,7 @@ impl Database {
|
|||||||
if arc.is_none() {
|
if arc.is_none() {
|
||||||
arc = Some(Arc::new(update.clone()));
|
arc = Some(Arc::new(update.clone()));
|
||||||
}
|
}
|
||||||
func(arc.as_ref().unwrap())
|
func(Arc::clone(arc.as_ref().unwrap()))
|
||||||
}
|
}
|
||||||
UpdateEndpoint::CustomBytes(func) => {
|
UpdateEndpoint::CustomBytes(func) => {
|
||||||
if bytes.is_none() {
|
if bytes.is_none() {
|
||||||
@ -1028,6 +1079,9 @@ impl Database {
|
|||||||
self.update_endpoints.remove(i);
|
self.update_endpoints.remove(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(req) = req {
|
||||||
|
update.action.put_req(req);
|
||||||
|
}
|
||||||
update.action
|
update.action
|
||||||
}
|
}
|
||||||
pub fn sync(&mut self, artists: Vec<Artist>, albums: Vec<Album>, songs: Vec<Song>) {
|
pub fn sync(&mut self, artists: Vec<Artist>, albums: Vec<Album>, songs: Vec<Song>) {
|
||||||
@ -1078,6 +1132,11 @@ pub struct Cover {
|
|||||||
pub location: DatabaseLocation,
|
pub location: DatabaseLocation,
|
||||||
pub data: Arc<Mutex<(bool, Option<(Instant, Vec<u8>)>)>>,
|
pub data: Arc<Mutex<(bool, Option<(Instant, Vec<u8>)>)>>,
|
||||||
}
|
}
|
||||||
|
impl PartialEq for Cover {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.location == other.location
|
||||||
|
}
|
||||||
|
}
|
||||||
impl Cover {
|
impl Cover {
|
||||||
pub fn get_bytes_from_file<O>(
|
pub fn get_bytes_from_file<O>(
|
||||||
&self,
|
&self,
|
||||||
|
@ -17,13 +17,13 @@ pub type AlbumId = u64;
|
|||||||
pub type ArtistId = u64;
|
pub type ArtistId = u64;
|
||||||
pub type CoverId = u64;
|
pub type CoverId = u64;
|
||||||
|
|
||||||
#[derive(Clone, Default, Debug)]
|
#[derive(Clone, Default, Debug, PartialEq)]
|
||||||
/// general data for songs, albums and artists
|
/// general data for songs, albums and artists
|
||||||
pub struct GeneralData {
|
pub struct GeneralData {
|
||||||
pub tags: Vec<String>,
|
pub tags: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
/// the location of a file relative to the lib directory, often Artist/Album/Song.ext or similar
|
/// the location of a file relative to the lib directory, often Artist/Album/Song.ext or similar
|
||||||
pub struct DatabaseLocation {
|
pub struct DatabaseLocation {
|
||||||
pub rel_path: PathBuf,
|
pub rel_path: PathBuf,
|
||||||
|
@ -4,18 +4,18 @@ use crate::load::ToFromBytes;
|
|||||||
|
|
||||||
use super::{database::Database, SongId};
|
use super::{database::Database, SongId};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct Queue {
|
pub struct Queue {
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
content: QueueContent,
|
content: QueueContent,
|
||||||
}
|
}
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum QueueContent {
|
pub enum QueueContent {
|
||||||
Song(SongId),
|
Song(SongId),
|
||||||
Folder(QueueFolder),
|
Folder(QueueFolder),
|
||||||
Loop(usize, usize, Box<Queue>),
|
Loop(usize, usize, Box<Queue>),
|
||||||
}
|
}
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
pub struct QueueFolder {
|
pub struct QueueFolder {
|
||||||
pub index: usize,
|
pub index: usize,
|
||||||
pub content: Vec<Queue>,
|
pub content: Vec<Queue>,
|
||||||
|
@ -17,7 +17,7 @@ use super::{
|
|||||||
AlbumId, ArtistId, CoverId, DatabaseLocation, GeneralData, SongId,
|
AlbumId, ArtistId, CoverId, DatabaseLocation, GeneralData, SongId,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct Song {
|
pub struct Song {
|
||||||
pub id: SongId,
|
pub id: SongId,
|
||||||
pub location: DatabaseLocation,
|
pub location: DatabaseLocation,
|
||||||
@ -303,6 +303,12 @@ pub struct CachedData(
|
|||||||
)>,
|
)>,
|
||||||
>,
|
>,
|
||||||
);
|
);
|
||||||
|
impl PartialEq for CachedData {
|
||||||
|
fn eq(&self, _other: &Self) -> bool {
|
||||||
|
// for testing
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
impl Clone for CachedData {
|
impl Clone for CachedData {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self(Arc::clone(&self.0))
|
Self(Arc::clone(&self.0))
|
||||||
|
@ -126,7 +126,7 @@ impl<T: PlayerBackend<SongCustomData>> Player<T> {
|
|||||||
pub fn update_uncache_opt(&mut self, db: &mut Database, allow_uncaching: bool) {
|
pub fn update_uncache_opt(&mut self, db: &mut Database, allow_uncaching: bool) {
|
||||||
if self.allow_sending_commands {
|
if self.allow_sending_commands {
|
||||||
if self.allow_sending_commands && self.backend.song_finished() {
|
if self.allow_sending_commands && self.backend.song_finished() {
|
||||||
db.apply_action_unchecked_seq(Action::NextSong);
|
db.apply_action_unchecked_seq(Action::NextSong, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,7 +145,7 @@ impl<T: PlayerBackend<SongCustomData>> Player<T> {
|
|||||||
self.backend.next(db.playing, load_duration);
|
self.backend.next(db.playing, load_duration);
|
||||||
if self.allow_sending_commands && load_duration {
|
if self.allow_sending_commands && load_duration {
|
||||||
if let Some(dur) = self.backend.current_song_duration() {
|
if let Some(dur) = self.backend.current_song_duration() {
|
||||||
db.apply_action_unchecked_seq(Action::SetSongDuration(id, dur))
|
db.apply_action_unchecked_seq(Action::SetSongDuration(id, dur), None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let Some(song) = db.get_song(&id) {
|
} else if let Some(song) = db.get_song(&id) {
|
||||||
@ -169,21 +169,27 @@ impl<T: PlayerBackend<SongCustomData>> Player<T> {
|
|||||||
self.backend.next(db.playing, load_duration);
|
self.backend.next(db.playing, load_duration);
|
||||||
if self.allow_sending_commands && load_duration {
|
if self.allow_sending_commands && load_duration {
|
||||||
if let Some(dur) = self.backend.current_song_duration() {
|
if let Some(dur) = self.backend.current_song_duration() {
|
||||||
db.apply_action_unchecked_seq(Action::SetSongDuration(id, dur))
|
db.apply_action_unchecked_seq(
|
||||||
|
Action::SetSongDuration(id, dur),
|
||||||
|
None,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// only show an error if the user tries to play the song.
|
// only show an error if the user tries to play the song.
|
||||||
// otherwise, the error might be spammed.
|
// otherwise, the error might be spammed.
|
||||||
if self.allow_sending_commands && db.playing {
|
if self.allow_sending_commands && db.playing {
|
||||||
db.apply_action_unchecked_seq(Action::ErrorInfo(
|
db.apply_action_unchecked_seq(
|
||||||
format!("Couldn't load bytes for song {id}"),
|
Action::ErrorInfo(
|
||||||
format!(
|
format!("Couldn't load bytes for song {id}"),
|
||||||
"Song: {}\nby {:?} on {:?}",
|
format!(
|
||||||
song.title, song.artist, song.album
|
"Song: {}\nby {:?} on {:?}",
|
||||||
|
song.title, song.artist, song.album
|
||||||
|
),
|
||||||
),
|
),
|
||||||
));
|
None,
|
||||||
db.apply_action_unchecked_seq(Action::NextSong);
|
);
|
||||||
|
db.apply_action_unchecked_seq(Action::NextSong, None);
|
||||||
}
|
}
|
||||||
self.backend.clear();
|
self.backend.clear();
|
||||||
}
|
}
|
||||||
|
@ -13,12 +13,12 @@ pub struct PlayerBackendPlaybackRs<T> {
|
|||||||
player: playback_rs::Player,
|
player: playback_rs::Player,
|
||||||
current: Option<(SongId, Option<playback_rs::Song>, T)>,
|
current: Option<(SongId, Option<playback_rs::Song>, T)>,
|
||||||
next: Option<(SongId, Option<playback_rs::Song>, T)>,
|
next: Option<(SongId, Option<playback_rs::Song>, T)>,
|
||||||
command_sender: Option<std::sync::mpsc::Sender<Command>>,
|
command_sender: Option<std::sync::mpsc::Sender<(Command, Option<u64>)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> PlayerBackendPlaybackRs<T> {
|
impl<T> PlayerBackendPlaybackRs<T> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
command_sender: std::sync::mpsc::Sender<Command>,
|
command_sender: std::sync::mpsc::Sender<(Command, Option<u64>)>,
|
||||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
Self::new_with_optional_command_sending(Some(command_sender))
|
Self::new_with_optional_command_sending(Some(command_sender))
|
||||||
}
|
}
|
||||||
@ -26,7 +26,7 @@ impl<T> PlayerBackendPlaybackRs<T> {
|
|||||||
Self::new_with_optional_command_sending(None)
|
Self::new_with_optional_command_sending(None)
|
||||||
}
|
}
|
||||||
pub fn new_with_optional_command_sending(
|
pub fn new_with_optional_command_sending(
|
||||||
command_sender: Option<std::sync::mpsc::Sender<Command>>,
|
command_sender: Option<std::sync::mpsc::Sender<(Command, Option<u64>)>>,
|
||||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
player: playback_rs::Player::new(None)?,
|
player: playback_rs::Player::new(None)?,
|
||||||
@ -55,13 +55,14 @@ impl<T> PlayerBackend<T> for PlayerBackendPlaybackRs<T> {
|
|||||||
Ok(v) => Some(v),
|
Ok(v) => Some(v),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if let Some(s) = &self.command_sender {
|
if let Some(s) = &self.command_sender {
|
||||||
s.send(
|
s.send((
|
||||||
Action::ErrorInfo(
|
Action::ErrorInfo(
|
||||||
format!("Couldn't decode song #{id}!"),
|
format!("Couldn't decode song #{id}!"),
|
||||||
format!("Error: {e}"),
|
format!("Error: {e}"),
|
||||||
)
|
)
|
||||||
.cmd(0xFFu8),
|
.cmd(0xFFu8),
|
||||||
)
|
None,
|
||||||
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
@ -101,21 +102,22 @@ impl<T> PlayerBackend<T> for PlayerBackendPlaybackRs<T> {
|
|||||||
if let Some(song) = song {
|
if let Some(song) = song {
|
||||||
if let Err(e) = self.player.play_song_now(song, None) {
|
if let Err(e) = self.player.play_song_now(song, None) {
|
||||||
if let Some(s) = &self.command_sender {
|
if let Some(s) = &self.command_sender {
|
||||||
s.send(
|
s.send((
|
||||||
Action::ErrorInfo(
|
Action::ErrorInfo(
|
||||||
format!("Couldn't play song #{id}!"),
|
format!("Couldn't play song #{id}!"),
|
||||||
format!("Error: {e}"),
|
format!("Error: {e}"),
|
||||||
)
|
)
|
||||||
.cmd(0xFFu8),
|
.cmd(0xFFu8),
|
||||||
)
|
None,
|
||||||
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
s.send(Action::NextSong.cmd(0xFFu8)).unwrap();
|
s.send((Action::NextSong.cmd(0xFFu8), None)).unwrap();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.player.set_playing(play);
|
self.player.set_playing(play);
|
||||||
}
|
}
|
||||||
} else if let Some(s) = &self.command_sender {
|
} else if let Some(s) = &self.command_sender {
|
||||||
s.send(Action::NextSong.cmd(0xFFu8)).unwrap();
|
s.send((Action::NextSong.cmd(0xFFu8), None)).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,12 +19,12 @@ pub struct PlayerBackendRodio<T> {
|
|||||||
stopped: bool,
|
stopped: bool,
|
||||||
current: Option<(SongId, Arc<Vec<u8>>, Option<u128>, T)>,
|
current: Option<(SongId, Arc<Vec<u8>>, Option<u128>, T)>,
|
||||||
next: Option<(SongId, Arc<Vec<u8>>, Option<MyDecoder>, T)>,
|
next: Option<(SongId, Arc<Vec<u8>>, Option<MyDecoder>, T)>,
|
||||||
command_sender: Option<std::sync::mpsc::Sender<Command>>,
|
command_sender: Option<std::sync::mpsc::Sender<(Command, Option<u64>)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> PlayerBackendRodio<T> {
|
impl<T> PlayerBackendRodio<T> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
command_sender: std::sync::mpsc::Sender<Command>,
|
command_sender: std::sync::mpsc::Sender<(Command, Option<u64>)>,
|
||||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
Self::new_with_optional_command_sending(Some(command_sender))
|
Self::new_with_optional_command_sending(Some(command_sender))
|
||||||
}
|
}
|
||||||
@ -32,7 +32,7 @@ impl<T> PlayerBackendRodio<T> {
|
|||||||
Self::new_with_optional_command_sending(None)
|
Self::new_with_optional_command_sending(None)
|
||||||
}
|
}
|
||||||
pub fn new_with_optional_command_sending(
|
pub fn new_with_optional_command_sending(
|
||||||
command_sender: Option<std::sync::mpsc::Sender<Command>>,
|
command_sender: Option<std::sync::mpsc::Sender<(Command, Option<u64>)>>,
|
||||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
let (output_stream, output_stream_handle) = rodio::OutputStream::try_default()?;
|
let (output_stream, output_stream_handle) = rodio::OutputStream::try_default()?;
|
||||||
let sink = Sink::try_new(&output_stream_handle)?;
|
let sink = Sink::try_new(&output_stream_handle)?;
|
||||||
@ -60,13 +60,14 @@ impl<T> PlayerBackend<T> for PlayerBackendRodio<T> {
|
|||||||
let decoder = decoder_from_bytes(Arc::clone(&bytes));
|
let decoder = decoder_from_bytes(Arc::clone(&bytes));
|
||||||
if let Err(e) = &decoder {
|
if let Err(e) = &decoder {
|
||||||
if let Some(s) = &self.command_sender {
|
if let Some(s) = &self.command_sender {
|
||||||
s.send(
|
s.send((
|
||||||
Action::ErrorInfo(
|
Action::ErrorInfo(
|
||||||
format!("Couldn't decode song #{id}!"),
|
format!("Couldn't decode song #{id}!"),
|
||||||
format!("Error: '{e}'"),
|
format!("Error: '{e}'"),
|
||||||
)
|
)
|
||||||
.cmd(0xFFu8),
|
.cmd(0xFFu8),
|
||||||
)
|
None,
|
||||||
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,11 +43,73 @@ impl Action {
|
|||||||
pub fn cmd(self, seq: u8) -> Command {
|
pub fn cmd(self, seq: u8) -> Command {
|
||||||
Command::new(seq, self)
|
Command::new(seq, self)
|
||||||
}
|
}
|
||||||
|
pub fn take_req(&mut self) -> Option<Req> {
|
||||||
|
self.req_mut()
|
||||||
|
.map(|r| std::mem::replace(r, Req::none()))
|
||||||
|
.filter(|r| r.is_some())
|
||||||
|
}
|
||||||
|
pub fn get_req(&mut self) -> Option<Req> {
|
||||||
|
self.req_mut().map(|r| *r).filter(|r| r.is_some())
|
||||||
|
}
|
||||||
|
pub fn put_req(&mut self, req: Req) {
|
||||||
|
if let Some(r) = self.req_mut() {
|
||||||
|
*r = req;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn req_mut(&mut self) -> Option<&mut Req> {
|
||||||
|
match self {
|
||||||
|
Self::QueueUpdate(_, _, req)
|
||||||
|
| Self::QueueAdd(_, _, req)
|
||||||
|
| Self::QueueInsert(_, _, _, req)
|
||||||
|
| Self::AddSong(_, req)
|
||||||
|
| Self::AddAlbum(_, req)
|
||||||
|
| Self::AddArtist(_, req)
|
||||||
|
| Self::AddCover(_, req)
|
||||||
|
| Self::ModifySong(_, req)
|
||||||
|
| Self::ModifyAlbum(_, req)
|
||||||
|
| Self::ModifyArtist(_, req)
|
||||||
|
| Self::Denied(req) => Some(req),
|
||||||
|
Self::Resume
|
||||||
|
| Self::Pause
|
||||||
|
| Self::Stop
|
||||||
|
| Self::NextSong
|
||||||
|
| Self::SyncDatabase(_, _, _)
|
||||||
|
| Self::QueueRemove(_)
|
||||||
|
| Self::QueueMove(_, _)
|
||||||
|
| Self::QueueMoveInto(_, _)
|
||||||
|
| Self::QueueGoto(_)
|
||||||
|
| Self::QueueShuffle(_)
|
||||||
|
| Self::QueueSetShuffle(_, _)
|
||||||
|
| Self::QueueUnshuffle(_)
|
||||||
|
| Self::RemoveSong(_)
|
||||||
|
| Self::RemoveAlbum(_)
|
||||||
|
| Self::RemoveArtist(_)
|
||||||
|
| Self::SetSongDuration(_, _)
|
||||||
|
| Self::TagSongFlagSet(_, _)
|
||||||
|
| Self::TagSongFlagUnset(_, _)
|
||||||
|
| Self::TagAlbumFlagSet(_, _)
|
||||||
|
| Self::TagAlbumFlagUnset(_, _)
|
||||||
|
| Self::TagArtistFlagSet(_, _)
|
||||||
|
| Self::TagArtistFlagUnset(_, _)
|
||||||
|
| Self::TagSongPropertySet(_, _, _)
|
||||||
|
| Self::TagSongPropertyUnset(_, _)
|
||||||
|
| Self::TagAlbumPropertySet(_, _, _)
|
||||||
|
| Self::TagAlbumPropertyUnset(_, _)
|
||||||
|
| Self::TagArtistPropertySet(_, _, _)
|
||||||
|
| Self::TagArtistPropertyUnset(_, _)
|
||||||
|
| Self::InitComplete
|
||||||
|
| Self::Save
|
||||||
|
| Self::ErrorInfo(_, _) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/// Should be stored in the same lock as the database
|
/// Should be stored in the same lock as the database
|
||||||
pub struct Commander {
|
pub struct Commander {
|
||||||
seq: u8,
|
seq: u8,
|
||||||
}
|
}
|
||||||
|
pub struct Requester {
|
||||||
|
req: u8,
|
||||||
|
}
|
||||||
impl Commander {
|
impl Commander {
|
||||||
pub fn new(ff: bool) -> Self {
|
pub fn new(ff: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -72,16 +134,43 @@ impl Commander {
|
|||||||
self.seq
|
self.seq
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(Clone, Debug)]
|
impl Requester {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { req: 0 }
|
||||||
|
}
|
||||||
|
pub fn inc(&mut self) -> Req {
|
||||||
|
if self.req < 0xFFu8 {
|
||||||
|
self.req += 1;
|
||||||
|
} else {
|
||||||
|
self.req = 1;
|
||||||
|
}
|
||||||
|
Req(self.req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A request ID, or None
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct Req(u8);
|
||||||
|
impl Req {
|
||||||
|
pub fn none() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
pub fn is_none(self) -> bool {
|
||||||
|
self.0 == 0
|
||||||
|
}
|
||||||
|
pub fn is_some(self) -> bool {
|
||||||
|
self.0 != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
Resume,
|
Resume,
|
||||||
Pause,
|
Pause,
|
||||||
Stop,
|
Stop,
|
||||||
NextSong,
|
NextSong,
|
||||||
SyncDatabase(Vec<Artist>, Vec<Album>, Vec<Song>),
|
SyncDatabase(Vec<Artist>, Vec<Album>, Vec<Song>),
|
||||||
QueueUpdate(Vec<usize>, Queue),
|
QueueUpdate(Vec<usize>, Queue, Req),
|
||||||
QueueAdd(Vec<usize>, Vec<Queue>),
|
QueueAdd(Vec<usize>, Vec<Queue>, Req),
|
||||||
QueueInsert(Vec<usize>, usize, Vec<Queue>),
|
QueueInsert(Vec<usize>, usize, Vec<Queue>, Req),
|
||||||
QueueRemove(Vec<usize>),
|
QueueRemove(Vec<usize>),
|
||||||
/// Move an element from A to B
|
/// Move an element from A to B
|
||||||
QueueMove(Vec<usize>, Vec<usize>),
|
QueueMove(Vec<usize>, Vec<usize>),
|
||||||
@ -95,18 +184,18 @@ pub enum Action {
|
|||||||
QueueUnshuffle(Vec<usize>),
|
QueueUnshuffle(Vec<usize>),
|
||||||
|
|
||||||
/// .id field is ignored!
|
/// .id field is ignored!
|
||||||
AddSong(Song),
|
AddSong(Song, Req),
|
||||||
/// .id field is ignored!
|
/// .id field is ignored!
|
||||||
AddAlbum(Album),
|
AddAlbum(Album, Req),
|
||||||
/// .id field is ignored!
|
/// .id field is ignored!
|
||||||
AddArtist(Artist),
|
AddArtist(Artist, Req),
|
||||||
AddCover(Cover),
|
AddCover(Cover, Req),
|
||||||
ModifySong(Song),
|
ModifySong(Song, Req),
|
||||||
ModifyAlbum(Album),
|
ModifyAlbum(Album, Req),
|
||||||
|
ModifyArtist(Artist, Req),
|
||||||
RemoveSong(SongId),
|
RemoveSong(SongId),
|
||||||
RemoveAlbum(AlbumId),
|
RemoveAlbum(AlbumId),
|
||||||
RemoveArtist(ArtistId),
|
RemoveArtist(ArtistId),
|
||||||
ModifyArtist(Artist),
|
|
||||||
SetSongDuration(SongId, u64),
|
SetSongDuration(SongId, u64),
|
||||||
/// Add the given Tag to the song's tags, if it isn't set already.
|
/// Add the given Tag to the song's tags, if it isn't set already.
|
||||||
TagSongFlagSet(SongId, String),
|
TagSongFlagSet(SongId, String),
|
||||||
@ -129,21 +218,25 @@ pub enum Action {
|
|||||||
InitComplete,
|
InitComplete,
|
||||||
Save,
|
Save,
|
||||||
ErrorInfo(String, String),
|
ErrorInfo(String, String),
|
||||||
|
|
||||||
|
/// The server denied a request or an action.
|
||||||
|
/// Contains the Request ID that was rejected, if there was a request ID.
|
||||||
|
Denied(Req),
|
||||||
}
|
}
|
||||||
impl Command {
|
impl Command {
|
||||||
pub fn send_to_server(self, db: &Database) -> Result<(), Self> {
|
pub fn send_to_server(self, db: &Database, client: Option<u64>) -> Result<(), Self> {
|
||||||
if let Some(sender) = &db.command_sender {
|
if let Some(sender) = &db.command_sender {
|
||||||
sender.send(self).unwrap();
|
sender.send((self, client)).unwrap();
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(self)
|
Err(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn send_to_server_or_apply(self, db: &mut Database) {
|
pub fn send_to_server_or_apply(self, db: &mut Database, client: Option<u64>) {
|
||||||
if let Some(sender) = &db.command_sender {
|
if let Some(sender) = &db.command_sender {
|
||||||
sender.send(self).unwrap();
|
sender.send((self, client)).unwrap();
|
||||||
} else {
|
} else {
|
||||||
db.apply_command(self);
|
db.apply_command(self, client);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,7 +257,7 @@ impl Command {
|
|||||||
pub fn run_server(
|
pub fn run_server(
|
||||||
database: Arc<Mutex<Database>>,
|
database: Arc<Mutex<Database>>,
|
||||||
addr_tcp: Option<SocketAddr>,
|
addr_tcp: Option<SocketAddr>,
|
||||||
sender_sender: Option<Box<dyn FnOnce(mpsc::Sender<Command>)>>,
|
sender_sender: Option<Box<dyn FnOnce(mpsc::Sender<(Command, Option<u64>)>)>>,
|
||||||
play_audio: bool,
|
play_audio: bool,
|
||||||
) {
|
) {
|
||||||
run_server_caching_thread_opt(database, addr_tcp, sender_sender, None, play_audio)
|
run_server_caching_thread_opt(database, addr_tcp, sender_sender, None, play_audio)
|
||||||
@ -172,7 +265,7 @@ pub fn run_server(
|
|||||||
pub fn run_server_caching_thread_opt(
|
pub fn run_server_caching_thread_opt(
|
||||||
database: Arc<Mutex<Database>>,
|
database: Arc<Mutex<Database>>,
|
||||||
addr_tcp: Option<SocketAddr>,
|
addr_tcp: Option<SocketAddr>,
|
||||||
sender_sender: Option<Box<dyn FnOnce(mpsc::Sender<Command>)>>,
|
sender_sender: Option<Box<dyn FnOnce(mpsc::Sender<(Command, Option<u64>)>)>>,
|
||||||
caching_thread: Option<Box<dyn FnOnce(&mut crate::data::cache_manager::CacheManager)>>,
|
caching_thread: Option<Box<dyn FnOnce(&mut crate::data::cache_manager::CacheManager)>>,
|
||||||
play_audio: bool,
|
play_audio: bool,
|
||||||
) {
|
) {
|
||||||
@ -251,6 +344,7 @@ pub fn run_server_caching_thread_opt(
|
|||||||
"control" => handle_one_connection_as_control(
|
"control" => handle_one_connection_as_control(
|
||||||
&mut connection,
|
&mut connection,
|
||||||
&command_sender,
|
&command_sender,
|
||||||
|
None,
|
||||||
),
|
),
|
||||||
"get" => _ = handle_one_connection_as_get(db, &mut connection),
|
"get" => _ = handle_one_connection_as_get(db, &mut connection),
|
||||||
_ => {
|
_ => {
|
||||||
@ -312,13 +406,13 @@ pub fn run_server_caching_thread_opt(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Ok(command) = command_receiver.recv_timeout(dur) {
|
if let Ok((command, client)) = command_receiver.recv_timeout(dur) {
|
||||||
checkf = true;
|
checkf = true;
|
||||||
#[cfg(feature = "playback")]
|
#[cfg(feature = "playback")]
|
||||||
if let Some(player) = &mut player {
|
if let Some(player) = &mut player {
|
||||||
player.handle_action(&command.action);
|
player.handle_action(&command.action);
|
||||||
}
|
}
|
||||||
database.lock().unwrap().apply_command(command);
|
database.lock().unwrap().apply_command(command, client);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -327,30 +421,36 @@ pub fn handle_one_connection_as_main(
|
|||||||
db: Arc<Mutex<Database>>,
|
db: Arc<Mutex<Database>>,
|
||||||
connection: &mut impl Read,
|
connection: &mut impl Read,
|
||||||
mut send_to: (impl Write + Sync + Send + 'static),
|
mut send_to: (impl Write + Sync + Send + 'static),
|
||||||
command_sender: &mpsc::Sender<Command>,
|
command_sender: &mpsc::Sender<(Command, Option<u64>)>,
|
||||||
) -> Result<(), std::io::Error> {
|
) -> Result<(), std::io::Error> {
|
||||||
// sync database
|
// sync database
|
||||||
let mut db = db.lock().unwrap();
|
let mut db = db.lock().unwrap();
|
||||||
db.init_connection(&mut send_to)?;
|
db.init_connection(&mut send_to)?;
|
||||||
// keep the client in sync:
|
// keep the client in sync:
|
||||||
// the db will send all updates to the client once it is added to update_endpoints
|
// the db will send all updates to the client once it is added to update_endpoints
|
||||||
db.update_endpoints.push(UpdateEndpoint::Bytes(Box::new(
|
let udepid = db.update_endpoints_id;
|
||||||
// try_clone is used here to split a TcpStream into Writer and Reader
|
db.update_endpoints_id += 1;
|
||||||
send_to,
|
db.update_endpoints.push((
|
||||||
)));
|
udepid,
|
||||||
|
UpdateEndpoint::Bytes(Box::new(
|
||||||
|
// try_clone is used here to split a TcpStream into Writer and Reader
|
||||||
|
send_to,
|
||||||
|
)),
|
||||||
|
));
|
||||||
// drop the mutex lock
|
// drop the mutex lock
|
||||||
drop(db);
|
drop(db);
|
||||||
handle_one_connection_as_control(connection, command_sender);
|
handle_one_connection_as_control(connection, command_sender, Some(udepid));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn handle_one_connection_as_control(
|
pub fn handle_one_connection_as_control(
|
||||||
connection: &mut impl Read,
|
connection: &mut impl Read,
|
||||||
command_sender: &mpsc::Sender<Command>,
|
command_sender: &mpsc::Sender<(Command, Option<u64>)>,
|
||||||
|
client: Option<u64>,
|
||||||
) {
|
) {
|
||||||
// read updates from the tcp stream and send them to the database, exit on EOF or Err
|
// read updates from the tcp stream and send them to the database, exit on EOF or Err
|
||||||
loop {
|
loop {
|
||||||
if let Ok(command) = Command::from_bytes(connection) {
|
if let Ok(command) = Command::from_bytes(connection) {
|
||||||
command_sender.send(command).unwrap();
|
command_sender.send((command, client)).unwrap();
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -375,6 +475,7 @@ const BYTE_INIT_COMPLETE: u8 = 0b01_010_000;
|
|||||||
const BYTE_SET_SONG_DURATION: u8 = 0b01_010_001;
|
const BYTE_SET_SONG_DURATION: u8 = 0b01_010_001;
|
||||||
const BYTE_SAVE: u8 = 0b01_010_010;
|
const BYTE_SAVE: u8 = 0b01_010_010;
|
||||||
const BYTE_ERRORINFO: u8 = 0b01_100_010;
|
const BYTE_ERRORINFO: u8 = 0b01_100_010;
|
||||||
|
const BYTE_DENIED: u8 = 0b01_100_011;
|
||||||
|
|
||||||
const BYTE_QUEUE_UPDATE: u8 = 0b10_000_000;
|
const BYTE_QUEUE_UPDATE: u8 = 0b10_000_000;
|
||||||
const BYTE_QUEUE_ADD: u8 = 0b10_000_001;
|
const BYTE_QUEUE_ADD: u8 = 0b10_000_001;
|
||||||
@ -430,6 +531,21 @@ impl ToFromBytes for Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToFromBytes for Req {
|
||||||
|
fn to_bytes<T>(&self, s: &mut T) -> Result<(), std::io::Error>
|
||||||
|
where
|
||||||
|
T: Write,
|
||||||
|
{
|
||||||
|
self.0.to_bytes(s)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn from_bytes<T>(s: &mut T) -> Result<Self, std::io::Error>
|
||||||
|
where
|
||||||
|
T: Read,
|
||||||
|
{
|
||||||
|
Ok(Self(ToFromBytes::from_bytes(s)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
// impl ToFromBytes for Action {
|
// impl ToFromBytes for Action {
|
||||||
impl Action {
|
impl Action {
|
||||||
fn to_bytes<T>(&self, s: &mut T) -> Result<(), std::io::Error>
|
fn to_bytes<T>(&self, s: &mut T) -> Result<(), std::io::Error>
|
||||||
@ -447,21 +563,24 @@ impl Action {
|
|||||||
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, req) => {
|
||||||
s.write_all(&[BYTE_QUEUE_UPDATE])?;
|
s.write_all(&[BYTE_QUEUE_UPDATE])?;
|
||||||
index.to_bytes(s)?;
|
index.to_bytes(s)?;
|
||||||
new_data.to_bytes(s)?;
|
new_data.to_bytes(s)?;
|
||||||
|
req.to_bytes(s)?;
|
||||||
}
|
}
|
||||||
Self::QueueAdd(index, new_data) => {
|
Self::QueueAdd(index, new_data, req) => {
|
||||||
s.write_all(&[BYTE_QUEUE_ADD])?;
|
s.write_all(&[BYTE_QUEUE_ADD])?;
|
||||||
index.to_bytes(s)?;
|
index.to_bytes(s)?;
|
||||||
new_data.to_bytes(s)?;
|
new_data.to_bytes(s)?;
|
||||||
|
req.to_bytes(s)?;
|
||||||
}
|
}
|
||||||
Self::QueueInsert(index, pos, new_data) => {
|
Self::QueueInsert(index, pos, new_data, req) => {
|
||||||
s.write_all(&[BYTE_QUEUE_INSERT])?;
|
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)?;
|
||||||
|
req.to_bytes(s)?;
|
||||||
}
|
}
|
||||||
Self::QueueRemove(index) => {
|
Self::QueueRemove(index) => {
|
||||||
s.write_all(&[BYTE_QUEUE_REMOVE])?;
|
s.write_all(&[BYTE_QUEUE_REMOVE])?;
|
||||||
@ -497,40 +616,47 @@ impl Action {
|
|||||||
s.write_all(&[SUBBYTE_ACTION_UNSHUFFLE])?;
|
s.write_all(&[SUBBYTE_ACTION_UNSHUFFLE])?;
|
||||||
path.to_bytes(s)?;
|
path.to_bytes(s)?;
|
||||||
}
|
}
|
||||||
Self::AddSong(song) => {
|
Self::AddSong(song, req) => {
|
||||||
s.write_all(&[BYTE_LIB_ADD])?;
|
s.write_all(&[BYTE_LIB_ADD])?;
|
||||||
s.write_all(&[SUBBYTE_SONG])?;
|
s.write_all(&[SUBBYTE_SONG])?;
|
||||||
song.to_bytes(s)?;
|
song.to_bytes(s)?;
|
||||||
|
req.to_bytes(s)?;
|
||||||
}
|
}
|
||||||
Self::AddAlbum(album) => {
|
Self::AddAlbum(album, req) => {
|
||||||
s.write_all(&[BYTE_LIB_ADD])?;
|
s.write_all(&[BYTE_LIB_ADD])?;
|
||||||
s.write_all(&[SUBBYTE_ALBUM])?;
|
s.write_all(&[SUBBYTE_ALBUM])?;
|
||||||
album.to_bytes(s)?;
|
album.to_bytes(s)?;
|
||||||
|
req.to_bytes(s)?;
|
||||||
}
|
}
|
||||||
Self::AddArtist(artist) => {
|
Self::AddArtist(artist, req) => {
|
||||||
s.write_all(&[BYTE_LIB_ADD])?;
|
s.write_all(&[BYTE_LIB_ADD])?;
|
||||||
s.write_all(&[SUBBYTE_ARTIST])?;
|
s.write_all(&[SUBBYTE_ARTIST])?;
|
||||||
artist.to_bytes(s)?;
|
artist.to_bytes(s)?;
|
||||||
|
req.to_bytes(s)?;
|
||||||
}
|
}
|
||||||
Self::AddCover(cover) => {
|
Self::AddCover(cover, req) => {
|
||||||
s.write_all(&[BYTE_LIB_ADD])?;
|
s.write_all(&[BYTE_LIB_ADD])?;
|
||||||
s.write_all(&[SUBBYTE_COVER])?;
|
s.write_all(&[SUBBYTE_COVER])?;
|
||||||
cover.to_bytes(s)?;
|
cover.to_bytes(s)?;
|
||||||
|
req.to_bytes(s)?;
|
||||||
}
|
}
|
||||||
Self::ModifySong(song) => {
|
Self::ModifySong(song, req) => {
|
||||||
s.write_all(&[BYTE_LIB_MODIFY])?;
|
s.write_all(&[BYTE_LIB_MODIFY])?;
|
||||||
s.write_all(&[SUBBYTE_SONG])?;
|
s.write_all(&[SUBBYTE_SONG])?;
|
||||||
song.to_bytes(s)?;
|
song.to_bytes(s)?;
|
||||||
|
req.to_bytes(s)?;
|
||||||
}
|
}
|
||||||
Self::ModifyAlbum(album) => {
|
Self::ModifyAlbum(album, req) => {
|
||||||
s.write_all(&[BYTE_LIB_MODIFY])?;
|
s.write_all(&[BYTE_LIB_MODIFY])?;
|
||||||
s.write_all(&[SUBBYTE_ALBUM])?;
|
s.write_all(&[SUBBYTE_ALBUM])?;
|
||||||
album.to_bytes(s)?;
|
album.to_bytes(s)?;
|
||||||
|
req.to_bytes(s)?;
|
||||||
}
|
}
|
||||||
Self::ModifyArtist(artist) => {
|
Self::ModifyArtist(artist, req) => {
|
||||||
s.write_all(&[BYTE_LIB_MODIFY])?;
|
s.write_all(&[BYTE_LIB_MODIFY])?;
|
||||||
s.write_all(&[SUBBYTE_ARTIST])?;
|
s.write_all(&[SUBBYTE_ARTIST])?;
|
||||||
artist.to_bytes(s)?;
|
artist.to_bytes(s)?;
|
||||||
|
req.to_bytes(s)?;
|
||||||
}
|
}
|
||||||
Self::RemoveSong(song) => {
|
Self::RemoveSong(song) => {
|
||||||
s.write_all(&[BYTE_LIB_REMOVE])?;
|
s.write_all(&[BYTE_LIB_REMOVE])?;
|
||||||
@ -636,6 +762,10 @@ impl Action {
|
|||||||
t.to_bytes(s)?;
|
t.to_bytes(s)?;
|
||||||
d.to_bytes(s)?;
|
d.to_bytes(s)?;
|
||||||
}
|
}
|
||||||
|
Self::Denied(req) => {
|
||||||
|
s.write_all(&[BYTE_DENIED])?;
|
||||||
|
req.to_bytes(s)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -654,9 +784,11 @@ impl Action {
|
|||||||
BYTE_STOP => Self::Stop,
|
BYTE_STOP => Self::Stop,
|
||||||
BYTE_NEXT_SONG => Self::NextSong,
|
BYTE_NEXT_SONG => Self::NextSong,
|
||||||
BYTE_SYNC_DATABASE => Self::SyncDatabase(from_bytes!(), from_bytes!(), from_bytes!()),
|
BYTE_SYNC_DATABASE => Self::SyncDatabase(from_bytes!(), from_bytes!(), from_bytes!()),
|
||||||
BYTE_QUEUE_UPDATE => Self::QueueUpdate(from_bytes!(), from_bytes!()),
|
BYTE_QUEUE_UPDATE => Self::QueueUpdate(from_bytes!(), from_bytes!(), from_bytes!()),
|
||||||
BYTE_QUEUE_ADD => Self::QueueAdd(from_bytes!(), from_bytes!()),
|
BYTE_QUEUE_ADD => Self::QueueAdd(from_bytes!(), from_bytes!(), from_bytes!()),
|
||||||
BYTE_QUEUE_INSERT => Self::QueueInsert(from_bytes!(), from_bytes!(), from_bytes!()),
|
BYTE_QUEUE_INSERT => {
|
||||||
|
Self::QueueInsert(from_bytes!(), from_bytes!(), from_bytes!(), from_bytes!())
|
||||||
|
}
|
||||||
BYTE_QUEUE_REMOVE => Self::QueueRemove(from_bytes!()),
|
BYTE_QUEUE_REMOVE => Self::QueueRemove(from_bytes!()),
|
||||||
BYTE_QUEUE_MOVE => Self::QueueMove(from_bytes!(), from_bytes!()),
|
BYTE_QUEUE_MOVE => Self::QueueMove(from_bytes!(), from_bytes!()),
|
||||||
BYTE_QUEUE_MOVE_INTO => Self::QueueMoveInto(from_bytes!(), from_bytes!()),
|
BYTE_QUEUE_MOVE_INTO => Self::QueueMoveInto(from_bytes!(), from_bytes!()),
|
||||||
@ -674,10 +806,10 @@ impl Action {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
BYTE_LIB_ADD => match s.read_byte()? {
|
BYTE_LIB_ADD => match s.read_byte()? {
|
||||||
SUBBYTE_SONG => Self::AddSong(from_bytes!()),
|
SUBBYTE_SONG => Self::AddSong(from_bytes!(), from_bytes!()),
|
||||||
SUBBYTE_ALBUM => Self::AddAlbum(from_bytes!()),
|
SUBBYTE_ALBUM => Self::AddAlbum(from_bytes!(), from_bytes!()),
|
||||||
SUBBYTE_ARTIST => Self::AddArtist(from_bytes!()),
|
SUBBYTE_ARTIST => Self::AddArtist(from_bytes!(), from_bytes!()),
|
||||||
SUBBYTE_COVER => Self::AddCover(from_bytes!()),
|
SUBBYTE_COVER => Self::AddCover(from_bytes!(), from_bytes!()),
|
||||||
_ => {
|
_ => {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"[{}] unexpected byte when reading command:libAdd; stopping playback.",
|
"[{}] unexpected byte when reading command:libAdd; stopping playback.",
|
||||||
@ -687,9 +819,9 @@ impl Action {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
BYTE_LIB_MODIFY => match s.read_byte()? {
|
BYTE_LIB_MODIFY => match s.read_byte()? {
|
||||||
SUBBYTE_SONG => Self::ModifySong(from_bytes!()),
|
SUBBYTE_SONG => Self::ModifySong(from_bytes!(), from_bytes!()),
|
||||||
SUBBYTE_ALBUM => Self::ModifyAlbum(from_bytes!()),
|
SUBBYTE_ALBUM => Self::ModifyAlbum(from_bytes!(), from_bytes!()),
|
||||||
SUBBYTE_ARTIST => Self::ModifyArtist(from_bytes!()),
|
SUBBYTE_ARTIST => Self::ModifyArtist(from_bytes!(), from_bytes!()),
|
||||||
_ => {
|
_ => {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"[{}] unexpected byte when reading command:libModify; stopping playback.",
|
"[{}] unexpected byte when reading command:libModify; stopping playback.",
|
||||||
@ -751,6 +883,7 @@ impl Action {
|
|||||||
BYTE_INIT_COMPLETE => Self::InitComplete,
|
BYTE_INIT_COMPLETE => Self::InitComplete,
|
||||||
BYTE_SAVE => Self::Save,
|
BYTE_SAVE => Self::Save,
|
||||||
BYTE_ERRORINFO => Self::ErrorInfo(from_bytes!(), from_bytes!()),
|
BYTE_ERRORINFO => Self::ErrorInfo(from_bytes!(), from_bytes!()),
|
||||||
|
BYTE_DENIED => Self::Denied(from_bytes!()),
|
||||||
_ => {
|
_ => {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"[{}] unexpected byte when reading command; stopping playback.",
|
"[{}] unexpected byte when reading command; stopping playback.",
|
||||||
@ -772,3 +905,61 @@ impl<T: Read> ReadByte for T {
|
|||||||
Ok(b[0])
|
Ok(b[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_to_from_bytes() {
|
||||||
|
use crate::data::queue::QueueContent;
|
||||||
|
use std::io::Cursor;
|
||||||
|
for v in [
|
||||||
|
Action::Resume,
|
||||||
|
Action::Pause,
|
||||||
|
Action::Stop,
|
||||||
|
Action::NextSong,
|
||||||
|
Action::SyncDatabase(vec![], vec![], vec![]),
|
||||||
|
Action::QueueUpdate(vec![], QueueContent::Song(12).into(), Req::none()),
|
||||||
|
Action::QueueAdd(vec![], vec![], Req::none()),
|
||||||
|
Action::QueueInsert(vec![], 5, vec![], Req::none()),
|
||||||
|
Action::QueueRemove(vec![]),
|
||||||
|
Action::QueueMove(vec![], vec![]),
|
||||||
|
Action::QueueMoveInto(vec![], vec![]),
|
||||||
|
Action::QueueGoto(vec![]),
|
||||||
|
Action::QueueShuffle(vec![]),
|
||||||
|
Action::QueueSetShuffle(vec![], vec![]),
|
||||||
|
Action::QueueUnshuffle(vec![]),
|
||||||
|
// Action::AddSong(Song, Req),
|
||||||
|
// Action::AddAlbum(Album, Req),
|
||||||
|
// Action::AddArtist(Artist, Req),
|
||||||
|
// Action::AddCover(Cover, Req),
|
||||||
|
// Action::ModifySong(Song, Req),
|
||||||
|
// Action::ModifyAlbum(Album, Req),
|
||||||
|
// Action::ModifyArtist(Artist, Req),
|
||||||
|
// Action::RemoveSong(SongId),
|
||||||
|
// Action::RemoveAlbum(AlbumId),
|
||||||
|
// Action::RemoveArtist(ArtistId),
|
||||||
|
// Action::SetSongDuration(SongId, u64),
|
||||||
|
// Action::TagSongFlagSet(SongId, String),
|
||||||
|
// Action::TagSongFlagUnset(SongId, String),
|
||||||
|
// Action::TagAlbumFlagSet(AlbumId, String),
|
||||||
|
// Action::TagAlbumFlagUnset(AlbumId, String),
|
||||||
|
// Action::TagArtistFlagSet(ArtistId, String),
|
||||||
|
// Action::TagArtistFlagUnset(ArtistId, String),
|
||||||
|
// Action::TagSongPropertySet(SongId, String, String),
|
||||||
|
// Action::TagSongPropertyUnset(SongId, String),
|
||||||
|
// Action::TagAlbumPropertySet(AlbumId, String, String),
|
||||||
|
// Action::TagAlbumPropertyUnset(AlbumId, String),
|
||||||
|
// Action::TagArtistPropertySet(ArtistId, String, String),
|
||||||
|
// Action::TagArtistPropertyUnset(ArtistId, String),
|
||||||
|
Action::InitComplete,
|
||||||
|
Action::Save,
|
||||||
|
Action::ErrorInfo(format!("some error"), format!("with a message")),
|
||||||
|
Action::Denied(Req::none()),
|
||||||
|
] {
|
||||||
|
let v = v.cmd(0xFF);
|
||||||
|
assert_eq!(
|
||||||
|
v.action,
|
||||||
|
Command::from_bytes(&mut Cursor::new(v.to_bytes_vec()))
|
||||||
|
.unwrap()
|
||||||
|
.action
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
# compile for aarch64 linux
|
# # compile for aarch64 linux
|
||||||
[build]
|
[build]
|
||||||
pre-build = [
|
pre-build = [
|
||||||
"dpkg --add-architecture $CROSS_DEB_ARCH",
|
"dpkg --add-architecture $CROSS_DEB_ARCH",
|
||||||
"apt-get update && apt-get --assume-yes install libasound2-dev libasound2-dev:$CROSS_DEB_ARCH"
|
"apt-get update && apt-get --assume-yes install --force musl-dev libasound2-dev"
|
||||||
]
|
]
|
||||||
default-target = "aarch64-unknown-linux-gnu"
|
default-target = "x86_64-unknown-linux-musl"
|
||||||
|
|
||||||
# # compile for aarch64 android
|
# compile for aarch64 android
|
||||||
# [build]
|
# [build]
|
||||||
# pre-build = [
|
# pre-build = [
|
||||||
# "dpkg --add-architecture $CROSS_DEB_ARCH",
|
# "dpkg --add-architecture $CROSS_DEB_ARCH",
|
||||||
|
@ -151,11 +151,11 @@ fn main() {
|
|||||||
let cmd = musicdb_lib::server::Command::from_bytes(&mut con).unwrap();
|
let cmd = musicdb_lib::server::Command::from_bytes(&mut con).unwrap();
|
||||||
use musicdb_lib::server::Action::*;
|
use musicdb_lib::server::Action::*;
|
||||||
match &cmd.action {
|
match &cmd.action {
|
||||||
// ignore playback and queue commands
|
// ignore playback and queue commands, and denials
|
||||||
Resume | Pause | Stop | NextSong | QueueUpdate(..) | QueueAdd(..)
|
Resume | Pause | Stop | NextSong | QueueUpdate(..) | QueueAdd(..)
|
||||||
| QueueInsert(..) | QueueRemove(..) | QueueMove(..) | QueueMoveInto(..)
|
| QueueInsert(..) | QueueRemove(..) | QueueMove(..) | QueueMoveInto(..)
|
||||||
| QueueGoto(..) | QueueShuffle(..) | QueueSetShuffle(..)
|
| QueueGoto(..) | QueueShuffle(..) | QueueSetShuffle(..)
|
||||||
| QueueUnshuffle(..) => continue,
|
| QueueUnshuffle(..) | Denied(..) => continue,
|
||||||
SyncDatabase(..)
|
SyncDatabase(..)
|
||||||
| AddSong(..)
|
| AddSong(..)
|
||||||
| AddAlbum(..)
|
| AddAlbum(..)
|
||||||
@ -184,7 +184,7 @@ fn main() {
|
|||||||
| Save
|
| Save
|
||||||
| ErrorInfo(..) => (),
|
| ErrorInfo(..) => (),
|
||||||
}
|
}
|
||||||
database.lock().unwrap().apply_command(cmd);
|
database.lock().unwrap().apply_command(cmd, None);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ use musicdb_lib::data::database::Database;
|
|||||||
use musicdb_lib::data::queue::{Queue, QueueContent, QueueFolder};
|
use musicdb_lib::data::queue::{Queue, QueueContent, QueueFolder};
|
||||||
use musicdb_lib::data::song::Song;
|
use musicdb_lib::data::song::Song;
|
||||||
use musicdb_lib::data::SongId;
|
use musicdb_lib::data::SongId;
|
||||||
use musicdb_lib::server::{Action, Command};
|
use musicdb_lib::server::{Action, Command, Req};
|
||||||
use rocket::response::content::RawHtml;
|
use rocket::response::content::RawHtml;
|
||||||
use rocket::{get, routes, Config, State};
|
use rocket::{get, routes, Config, State};
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ const HTML_END: &'static str = "</body></html>";
|
|||||||
|
|
||||||
struct Data {
|
struct Data {
|
||||||
db: Arc<Mutex<Database>>,
|
db: Arc<Mutex<Database>>,
|
||||||
command_sender: mpsc::Sender<Command>,
|
command_sender: mpsc::Sender<(Command, Option<u64>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
@ -258,7 +258,7 @@ fn gen_queue_html_impl(
|
|||||||
fn queue_remove(data: &State<Data>, path: &str) {
|
fn queue_remove(data: &State<Data>, path: &str) {
|
||||||
if let Some(path) = path.split('_').map(|v| v.parse().ok()).collect() {
|
if let Some(path) = path.split('_').map(|v| v.parse().ok()).collect() {
|
||||||
data.command_sender
|
data.command_sender
|
||||||
.send(Action::QueueRemove(path).cmd(0xFFu8))
|
.send((Action::QueueRemove(path).cmd(0xFFu8), None))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -266,7 +266,7 @@ fn queue_remove(data: &State<Data>, path: &str) {
|
|||||||
fn queue_goto(data: &State<Data>, path: &str) {
|
fn queue_goto(data: &State<Data>, path: &str) {
|
||||||
if let Some(path) = path.split('_').map(|v| v.parse().ok()).collect() {
|
if let Some(path) = path.split('_').map(|v| v.parse().ok()).collect() {
|
||||||
data.command_sender
|
data.command_sender
|
||||||
.send(Action::QueueGoto(path).cmd(0xFFu8))
|
.send((Action::QueueGoto(path).cmd(0xFFu8), None))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -274,27 +274,31 @@ fn queue_goto(data: &State<Data>, path: &str) {
|
|||||||
#[get("/play")]
|
#[get("/play")]
|
||||||
fn play(data: &State<Data>) {
|
fn play(data: &State<Data>) {
|
||||||
data.command_sender
|
data.command_sender
|
||||||
.send(Action::Resume.cmd(0xFFu8))
|
.send((Action::Resume.cmd(0xFFu8), None))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
#[get("/pause")]
|
#[get("/pause")]
|
||||||
fn pause(data: &State<Data>) {
|
fn pause(data: &State<Data>) {
|
||||||
data.command_sender.send(Action::Pause.cmd(0xFFu8)).unwrap();
|
data.command_sender
|
||||||
|
.send((Action::Pause.cmd(0xFFu8), None))
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
#[get("/stop")]
|
#[get("/stop")]
|
||||||
fn stop(data: &State<Data>) {
|
fn stop(data: &State<Data>) {
|
||||||
data.command_sender.send(Action::Stop.cmd(0xFFu8)).unwrap();
|
data.command_sender
|
||||||
|
.send((Action::Stop.cmd(0xFFu8), None))
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
#[get("/skip")]
|
#[get("/skip")]
|
||||||
fn skip(data: &State<Data>) {
|
fn skip(data: &State<Data>) {
|
||||||
data.command_sender
|
data.command_sender
|
||||||
.send(Action::NextSong.cmd(0xFFu8))
|
.send((Action::NextSong.cmd(0xFFu8), None))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
#[get("/clear-queue")]
|
#[get("/clear-queue")]
|
||||||
fn clear_queue(data: &State<Data>) {
|
fn clear_queue(data: &State<Data>) {
|
||||||
data.command_sender
|
data.command_sender
|
||||||
.send(
|
.send((
|
||||||
Action::QueueUpdate(
|
Action::QueueUpdate(
|
||||||
vec![],
|
vec![],
|
||||||
QueueContent::Folder(QueueFolder {
|
QueueContent::Folder(QueueFolder {
|
||||||
@ -304,16 +308,21 @@ fn clear_queue(data: &State<Data>) {
|
|||||||
order: None,
|
order: None,
|
||||||
})
|
})
|
||||||
.into(),
|
.into(),
|
||||||
|
Req::none(),
|
||||||
)
|
)
|
||||||
.cmd(0xFFu8),
|
.cmd(0xFFu8),
|
||||||
)
|
None,
|
||||||
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/add-song/<id>")]
|
#[get("/add-song/<id>")]
|
||||||
fn add_song(data: &State<Data>, id: SongId) {
|
fn add_song(data: &State<Data>, id: SongId) {
|
||||||
data.command_sender
|
data.command_sender
|
||||||
.send(Action::QueueAdd(vec![], vec![QueueContent::Song(id).into()]).cmd(0xFFu8))
|
.send((
|
||||||
|
Action::QueueAdd(vec![], vec![QueueContent::Song(id).into()], Req::none()).cmd(0xFFu8),
|
||||||
|
None,
|
||||||
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -538,7 +547,7 @@ fn search(
|
|||||||
|
|
||||||
pub async fn main(
|
pub async fn main(
|
||||||
db: Arc<Mutex<Database>>,
|
db: Arc<Mutex<Database>>,
|
||||||
command_sender: mpsc::Sender<Command>,
|
command_sender: mpsc::Sender<(Command, Option<u64>)>,
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
) {
|
) {
|
||||||
rocket::build()
|
rocket::build()
|
||||||
|
Loading…
Reference in New Issue
Block a user