#![allow(dead_code)] #![allow(unused_variables)] use std::{ io::{BufReader, Write}, net::TcpStream, path::PathBuf, sync::{Arc, Mutex}, thread, }; use clap::{Parser, Subcommand}; #[cfg(feature = "speedy2d")] use gui::GuiEvent; #[cfg(feature = "playback")] use musicdb_lib::data::cache_manager::CacheManager; #[cfg(feature = "playback")] use musicdb_lib::player::{Player, PlayerBackendFeat}; use musicdb_lib::{ data::{ database::{ClientIo, Database}, CoverId, SongId, }, load::ToFromBytes, server::Command, }; #[cfg(feature = "speedy2d")] use speedy2d::color::Color; #[cfg(feature = "speedy2d")] mod gui; #[cfg(feature = "speedy2d")] mod gui_anim; #[cfg(feature = "speedy2d")] mod gui_base; #[cfg(feature = "speedy2d")] mod gui_edit_song; #[cfg(feature = "speedy2d")] mod gui_idle_display; #[cfg(feature = "speedy2d")] mod gui_library; #[cfg(feature = "speedy2d")] mod gui_notif; #[cfg(feature = "speedy2d")] mod gui_playback; #[cfg(feature = "speedy2d")] mod gui_playpause; #[cfg(feature = "speedy2d")] mod gui_queue; #[cfg(feature = "speedy2d")] mod gui_screen; #[cfg(feature = "speedy2d")] mod gui_settings; #[cfg(feature = "speedy2d")] mod gui_song_adder; #[cfg(feature = "speedy2d")] mod gui_statusbar; #[cfg(feature = "speedy2d")] mod gui_text; #[cfg(feature = "speedy2d")] mod gui_wrappers; #[cfg(feature = "merscfg")] mod merscfg; #[cfg(feature = "speedy2d")] mod textcfg; #[derive(Parser, Debug)] struct Args { /// the address to be used for the tcp connection to the server addr: String, /// what to do #[command(subcommand)] mode: Mode, } #[derive(Subcommand, Debug, Clone)] enum Mode { /// 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 }, /// play in sync with the server, and fetch the songs from it too. slower than the local variant for obvious reasons #[cfg(feature = "playback")] SyncplayerNetwork, #[cfg(feature = "mers")] RunMers { path: PathBuf }, } fn get_config_file_path() -> PathBuf { directories::ProjectDirs::from("", "", "musicdb-client") .unwrap() .config_dir() .to_path_buf() } fn main() { #[cfg(not(feature = "speedy2d"))] #[cfg(not(feature = "mers"))] #[cfg(not(feature = "playback"))] compile_error!("None of the optional features are enabled. Without at least one of these, the application is useless! See Cargo.toml for info."); // parse args let args = Args::parse(); // start let addr = args.addr.clone(); let mut con = TcpStream::connect(&addr).unwrap(); let mode = args.mode; writeln!(con, "main").unwrap(); let database = Arc::new(Mutex::new(Database::new_clientside())); #[cfg(feature = "speedy2d")] let update_gui_sender: Arc>>> = Arc::new(Mutex::new(None)); #[cfg(feature = "speedy2d")] let sender = Arc::clone(&update_gui_sender); #[cfg(any(feature = "mers", feature = "merscfg"))] let mers_after_db_updated_action: Arc< Mutex>>, > = 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); let mut con = con.try_clone().unwrap(); // this is all you need to keep the db in sync let addr = addr.clone(); thread::spawn(move || { #[cfg(feature = "playback")] #[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)); cm.set_memory_mib(1024, 2048); cm.set_cache_songs_count(20); cache_manager = Some(cm); Some(Player::new_client( PlayerBackendFeat::new_without_command_sending().unwrap(), )) } else { None }; // prevent unused assignment warning, we might // need cache manager at some point -_- #[cfg(feature = "playback")] if false { drop(cache_manager); } #[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")] 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 = Box::new(TcpStream::connect(&addr).unwrap()); db.remote_server_as_song_file_source = Some(Arc::new(Mutex::new( musicdb_lib::server::get::Client::new(BufReader::new(client_con)).unwrap(), ))); } loop { let command = Command::from_bytes(&mut con).unwrap(); let mut db = database.lock().unwrap(); let action = db.seq.recv(command); #[cfg(feature = "playback")] if let Some(player) = &mut player { player.handle_action(&action); } #[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(action.clone()); action(action); break 'feature_if; } db.apply_action_unchecked_seq(action, None); } #[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(); } } }) }; macro_rules! gui_modes { () => {{ let get_con: Arc>>> = 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"), )); #[allow(unused_labels)] '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)); if let Some(v) = &*occasional_refresh_sender.lock().unwrap() { v.send_event(GuiEvent::Refresh).unwrap(); } }); gui::main( database, con, get_con, sender, #[cfg(feature = "merscfg")] &mers_after_db_updated_action, ) }}; } match mode { #[cfg(feature = "speedy2d")] #[cfg(feature = "playback")] Mode::Gui | Mode::GuiSyncplayerLocal { .. } | Mode::GuiSyncplayerNetwork => gui_modes!(), #[cfg(feature = "speedy2d")] #[cfg(not(feature = "playback"))] Mode::Gui => gui_modes!(), #[cfg(feature = "playback")] Mode::SyncplayerLocal { .. } | Mode::SyncplayerNetwork => { con_thread.join().unwrap(); } #[cfg(feature = "mers")] Mode::RunMers { path } => { let mut src = musicdb_mers::mers_lib::prelude_compile::Source::new_from_file(path).unwrap(); let srca = Arc::new(src.clone()); let con = Mutex::new(con); let (mut i1, mut i2, mut i3) = musicdb_mers::add( musicdb_mers::mers_lib::prelude_compile::Config::new().bundle_std(), &database, &Arc::new(move |cmd: Command| cmd.to_bytes(&mut *con.lock().unwrap()).unwrap()), &mers_after_db_updated_action, ) .infos(); let program = match musicdb_mers::mers_lib::prelude_compile::parse(&mut src, &srca) { Ok(p) => p, Err(e) => { eprintln!("{}", e.display_term()); std::process::exit(60); } }; let program = match program.compile(&mut i1, Default::default()) { Ok(p) => p, Err(e) => { eprintln!("{}", e.display_term()); std::process::exit(60); } }; match program.check(&mut i3, None) { Ok(_) => {} Err(e) => { eprintln!("{}", e.display_term()); std::process::exit(60); } }; // wait until db is synced let dur = std::time::Duration::from_secs_f32(0.1); loop { std::thread::sleep(dur); if database.lock().unwrap().is_client_init() { break; } } if let Err(e) = program.run(&mut i2) { eprintln!("{}", e.display_term()); } } } } pub fn accumulate Option, T>(mut f: F) -> Vec { let mut o = vec![]; loop { if let Some(v) = f() { o.push(v); } else { break; } } o } fn get_cover(song: SongId, database: &Database) -> Option { let song = database.get_song(&song)?; if let Some(v) = song.cover { Some(v) } else { database.albums().get(song.album.as_ref()?)?.cover } } #[cfg(feature = "speedy2d")] pub(crate) fn color_scale(c: Color, r: f32, g: f32, b: f32, new_alpha: Option) -> Color { Color::from_rgba(c.r() * r, c.g() * g, c.b() * b, new_alpha.unwrap_or(c.a())) }