add more modes to client and change server cli

server can now source its database and files from
another server, but it will have its own
queue and appears as separate to clients.

the client now has gui-syncplayer-{local,network}
modes which show the gui and also play the songs.
using a syncplayer-network mode now automatically
enables the cache manager, which should make
waiting for songs to load less frequent.
This commit is contained in:
Mark
2024-05-28 13:20:43 +02:00
parent 7d6d6d295b
commit 61110f5f4a
14 changed files with 405 additions and 139 deletions

View File

@@ -17,9 +17,9 @@ musicdb-mers = { version = "0.1.0", path = "../musicdb-mers", optional = true }
uianimator = "0.1.1"
[features]
default = ["gui", "mers", "merscfg"]
default = ["gui", "playback"]
# gui:
# enables the gui mode
# enables the gui modes
# merscfg:
# allows using mers to configure the gui
# mers:

View File

@@ -9,7 +9,12 @@ use std::{
};
use musicdb_lib::{
data::{database::Database, queue::Queue, song::Song, AlbumId, ArtistId, CoverId, SongId},
data::{
database::{ClientIo, Database},
queue::Queue,
song::Song,
AlbumId, ArtistId, CoverId, SongId,
},
load::ToFromBytes,
server::{get, Command},
};
@@ -79,9 +84,11 @@ pub fn hotkey_select_songs(modifiers: &ModifiersState, key: Option<VirtualKeyCod
pub fn main(
database: Arc<Mutex<Database>>,
connection: TcpStream,
get_con: get::Client<TcpStream>,
get_con: Arc<Mutex<get::Client<Box<dyn ClientIo + 'static>>>>,
event_sender_arc: Arc<Mutex<Option<UserEventSender<GuiEvent>>>>,
after_db_cmd: &Arc<Mutex<Option<Box<dyn FnMut(Command) + Send + Sync + 'static>>>>,
#[cfg(feature = "merscfg")] after_db_cmd: &Arc<
Mutex<Option<Box<dyn FnMut(Command) + Send + Sync + 'static>>>,
>,
) {
let config_dir = super::get_config_file_path();
let config_file = config_dir.join("config_gui.toml");
@@ -219,7 +226,7 @@ pub fn main(
font,
Arc::clone(&database),
connection,
Arc::new(Mutex::new(get_con)),
get_con,
event_sender_arc,
Arc::new(sender),
line_height,
@@ -264,6 +271,7 @@ pub fn main(
#[cfg(feature = "merscfg")]
merscfg: crate::merscfg::MersCfg::new(config_dir.join("dynamic_config.mers"), database),
},
#[cfg(feature = "merscfg")]
after_db_cmd,
));
}
@@ -284,7 +292,7 @@ pub struct Gui {
pub event_sender: Arc<UserEventSender<GuiEvent>>,
pub database: Arc<Mutex<Database>>,
pub connection: TcpStream,
pub get_con: Arc<Mutex<get::Client<TcpStream>>>,
pub get_con: Arc<Mutex<get::Client<Box<dyn ClientIo + 'static>>>>,
pub gui: GuiScreen,
pub notif_sender:
Sender<Box<dyn FnOnce(&NotifOverlay) -> (Box<dyn GuiElem>, NotifInfo) + Send>>,
@@ -316,7 +324,7 @@ impl Gui {
font: Font,
database: Arc<Mutex<Database>>,
connection: TcpStream,
get_con: Arc<Mutex<get::Client<TcpStream>>>,
get_con: Arc<Mutex<get::Client<Box<dyn ClientIo + 'static>>>>,
event_sender_arc: Arc<Mutex<Option<UserEventSender<GuiEvent>>>>,
event_sender: Arc<UserEventSender<GuiEvent>>,
line_height: f32,
@@ -1158,7 +1166,7 @@ pub struct DrawInfo<'a> {
/// true if `info.pos.contains(info.mouse_pos)`.
pub mouse_pos_in_bounds: bool,
pub helper: Option<&'a mut WindowHelper<GuiEvent>>,
pub get_con: Arc<Mutex<get::Client<TcpStream>>>,
pub get_con: Arc<Mutex<get::Client<Box<dyn ClientIo + 'static>>>>,
pub covers: &'a mut HashMap<CoverId, GuiServerImage>,
pub custom_images: &'a mut HashMap<String, GuiServerImage>,
pub has_keyboard_focus: bool,
@@ -1529,18 +1537,6 @@ impl WindowHandler<GuiEvent> for Gui {
scancode: KeyScancode,
) {
helper.request_redraw();
// handle keybinds unless settings are open, opening or closing
if self.gui.settings.0 == false && self.gui.settings.1.is_none() {
if let Some(key) = virtual_key_code {
let keybind = KeyBinding::new(&self.modifiers, key);
if let Some(action) = self.keybinds.get(&keybind) {
for a in self.key_actions.get(action).execute() {
self.exec_gui_action(a);
}
return;
}
}
}
if let Some(VirtualKeyCode::Tab) = virtual_key_code {
if !(self.modifiers.ctrl() || self.modifiers.alt() || self.modifiers.logo()) {
self.gui._keyboard_move_focus(self.modifiers.shift(), false);
@@ -1578,6 +1574,18 @@ impl WindowHandler<GuiEvent> for Gui {
scancode: KeyScancode,
) {
helper.request_redraw();
// handle keybinds unless settings are open, opening or closing
if self.gui.settings.0 == false && self.gui.settings.1.is_none() {
if let Some(key) = virtual_key_code {
let keybind = KeyBinding::new(&self.modifiers, key);
if let Some(action) = self.keybinds.get(&keybind) {
for a in self.key_actions.get(action).execute() {
self.exec_gui_action(a);
}
return;
}
}
}
for a in self.gui._keyboard_event(
&mut |e, a| {
if e.config().keyboard_events_focus {
@@ -1663,7 +1671,10 @@ pub enum GuiServerImage {
}
#[allow(unused)]
impl GuiServerImage {
pub fn new_cover(id: CoverId, get_con: Arc<Mutex<get::Client<TcpStream>>>) -> Self {
pub fn new_cover<T: ClientIo + 'static>(
id: CoverId,
get_con: Arc<Mutex<get::Client<T>>>,
) -> Self {
Self::Loading(std::thread::spawn(move || {
get_con
.lock()
@@ -1673,7 +1684,10 @@ impl GuiServerImage {
.and_then(|v| v.ok())
}))
}
pub fn new_custom_file(file: String, get_con: Arc<Mutex<get::Client<TcpStream>>>) -> Self {
pub fn new_custom_file<T: ClientIo + 'static>(
file: String,
get_con: Arc<Mutex<get::Client<T>>>,
) -> Self {
Self::Loading(std::thread::spawn(move || {
get_con
.lock()

View File

@@ -10,7 +10,9 @@ use clap::{Parser, Subcommand};
#[cfg(feature = "speedy2d")]
use gui::GuiEvent;
#[cfg(feature = "playback")]
use musicdb_lib::player::Player;
use musicdb_lib::data::cache_manager::CacheManager;
#[cfg(feature = "playback")]
use musicdb_lib::player::{rodio::PlayerBackendRodio, Player};
use musicdb_lib::{
data::{
database::{ClientIo, Database},
@@ -69,9 +71,17 @@ struct Args {
#[derive(Subcommand, Debug, Clone)]
enum Mode {
#[cfg(feature = "speedy2d")]
/// graphical user interface
#[cfg(feature = "speedy2d")]
Gui,
/// graphical user interface + syncplayer using local files (syncplayer-network can be enabled in settings)
#[cfg(feature = "speedy2d")]
#[cfg(feature = "playback")]
GuiSyncplayerLocal { lib_dir: PathBuf },
/// graphical user interface + syncplayer (syncplayer-network can be toggled in settings)
#[cfg(feature = "speedy2d")]
#[cfg(feature = "playback")]
GuiSyncplayerNetwork,
/// play in sync with the server, but load the songs from a local copy of the lib-dir
#[cfg(feature = "playback")]
SyncplayerLocal { lib_dir: PathBuf },
@@ -112,6 +122,7 @@ fn main() {
Mutex<Option<Box<dyn FnMut(Command) + Send + Sync + 'static>>>,
> = Arc::new(Mutex::new(None));
let con_thread = {
#[cfg(any(feature = "mers", feature = "merscfg"))]
let mers_after_db_updated_action = Arc::clone(&mers_after_db_updated_action);
let mode = mode.clone();
let database = Arc::clone(&database);
@@ -119,22 +130,52 @@ fn main() {
// this is all you need to keep the db in sync
thread::spawn(move || {
#[cfg(feature = "playback")]
let mut player =
if matches!(mode, Mode::SyncplayerLocal { .. } | Mode::SyncplayerNetwork) {
Some(Player::new().unwrap().without_sending_commands())
} else {
None
};
#[cfg(not(feature = "speedy2d"))]
let is_syncplayer =
matches!(mode, Mode::SyncplayerLocal { .. } | Mode::SyncplayerNetwork);
#[cfg(feature = "playback")]
#[cfg(feature = "speedy2d")]
let is_syncplayer = matches!(
mode,
Mode::SyncplayerLocal { .. }
| Mode::SyncplayerNetwork
| Mode::GuiSyncplayerLocal { .. }
| Mode::GuiSyncplayerNetwork
);
#[cfg(feature = "playback")]
let mut cache_manager = None;
#[cfg(feature = "playback")]
let mut player = if is_syncplayer {
let cm = CacheManager::new(Arc::clone(&database));
cache_manager = Some(cm);
Some(Player::new_client(
PlayerBackendRodio::new_without_command_sending().unwrap(),
))
} else {
None
};
#[allow(unused_labels)]
'ifstatementworkaround: {
// use if+break instead of if-else because we can't #[cfg(feature)] the if statement,
// since we want the else part to run if the feature is disabled
#[cfg(feature = "playback")]
if let Mode::SyncplayerLocal { lib_dir } = mode {
let lib_dir = match &mode {
Mode::SyncplayerLocal { lib_dir } => Some(lib_dir.clone()),
#[cfg(feature = "speedy2d")]
Mode::GuiSyncplayerLocal { lib_dir } => Some(lib_dir.clone()),
_ => None,
};
#[cfg(feature = "playback")]
if let Some(lib_dir) = lib_dir {
let mut db = database.lock().unwrap();
db.lib_directory = lib_dir;
break 'ifstatementworkaround;
}
#[cfg(feature = "speedy2d")]
if matches!(mode, Mode::Gui) {
// gui does this in the main thread
break 'ifstatementworkaround;
}
let mut db = database.lock().unwrap();
let client_con: Box<dyn ClientIo> = Box::new(TcpStream::connect(addr).unwrap());
db.remote_server_as_song_file_source = Some(Arc::new(Mutex::new(
@@ -142,26 +183,27 @@ fn main() {
)));
}
loop {
#[cfg(feature = "playback")]
if let Some(player) = &mut player {
let mut db = database.lock().unwrap();
if db.is_client_init() {
// command_sender does nothing. if a song finishes, we don't want to move to the next song, we want to wait for the server to send the NextSong event.
player.update(&mut db, &Arc::new(|_| {}));
}
}
let update = Command::from_bytes(&mut con).unwrap();
let mut db = database.lock().unwrap();
#[cfg(feature = "playback")]
if let Some(player) = &mut player {
player.handle_command(&update);
}
#[cfg(any(feature = "mers", feature = "merscfg"))]
if let Some(action) = &mut *mers_after_db_updated_action.lock().unwrap() {
database.lock().unwrap().apply_command(update.clone());
action(update);
} else {
database.lock().unwrap().apply_command(update);
#[allow(unused_labels)]
'feature_if: {
#[cfg(any(feature = "mers", feature = "merscfg"))]
if let Some(action) = &mut *mers_after_db_updated_action.lock().unwrap() {
db.apply_command(update.clone());
action(update);
break 'feature_if;
}
db.apply_command(update);
}
#[cfg(feature = "playback")]
if let Some(player) = &mut player {
player.update_dont_uncache(&mut *db);
}
drop(db);
#[cfg(feature = "speedy2d")]
if let Some(v) = &*update_gui_sender.lock().unwrap() {
v.send_event(GuiEvent::Refresh).unwrap();
@@ -171,8 +213,31 @@ fn main() {
};
match mode {
#[cfg(feature = "speedy2d")]
Mode::Gui => {
Mode::Gui | Mode::GuiSyncplayerLocal { .. } | Mode::GuiSyncplayerNetwork => {
{
let get_con: Arc<
Mutex<musicdb_lib::server::get::Client<Box<dyn ClientIo + 'static>>>,
> = Arc::new(Mutex::new(
musicdb_lib::server::get::Client::new(BufReader::new(Box::new(
TcpStream::connect(addr).expect("opening get client connection"),
)
as _))
.expect("initializing get client connection"),
));
'anotherifstatement: {
#[cfg(feature = "playback")]
if let Mode::GuiSyncplayerLocal { lib_dir } = mode {
database.lock().unwrap().lib_directory = lib_dir;
break 'anotherifstatement;
}
#[cfg(feature = "playback")]
if let Mode::GuiSyncplayerNetwork = mode {
break 'anotherifstatement;
}
// if not using syncplayer-local
database.lock().unwrap().remote_server_as_song_file_source =
Some(Arc::clone(&get_con));
}
let occasional_refresh_sender = Arc::clone(&sender);
thread::spawn(move || loop {
std::thread::sleep(std::time::Duration::from_secs(1));
@@ -183,10 +248,7 @@ fn main() {
gui::main(
database,
con,
musicdb_lib::server::get::Client::new(BufReader::new(
TcpStream::connect(addr).expect("opening get client connection"),
))
.expect("initializing get client connection"),
get_con,
sender,
#[cfg(feature = "merscfg")]
&mers_after_db_updated_action,