mirror of
https://github.com/Dummi26/musicdb.git
synced 2025-04-28 10:06:05 +02:00
245 lines
8.2 KiB
Rust
Executable File
245 lines
8.2 KiB
Rust
Executable File
use std::{
|
|
io::{BufReader, Write},
|
|
net::{SocketAddr, TcpStream},
|
|
path::PathBuf,
|
|
sync::{Arc, Mutex},
|
|
thread,
|
|
};
|
|
|
|
use clap::{Parser, Subcommand};
|
|
#[cfg(feature = "speedy2d")]
|
|
use gui::GuiEvent;
|
|
#[cfg(feature = "playback")]
|
|
use musicdb_lib::player::Player;
|
|
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_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: SocketAddr,
|
|
/// what to do
|
|
#[command(subcommand)]
|
|
mode: Mode,
|
|
}
|
|
|
|
#[derive(Subcommand, Debug, Clone)]
|
|
enum Mode {
|
|
#[cfg(feature = "speedy2d")]
|
|
/// graphical user interface
|
|
Gui,
|
|
/// 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;
|
|
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<Mutex<Option<speedy2d::window::UserEventSender<GuiEvent>>>> =
|
|
Arc::new(Mutex::new(None));
|
|
#[cfg(feature = "speedy2d")]
|
|
let sender = Arc::clone(&update_gui_sender);
|
|
let con_thread = {
|
|
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
|
|
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
|
|
};
|
|
#[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 mut db = database.lock().unwrap();
|
|
db.lib_directory = lib_dir;
|
|
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(
|
|
musicdb_lib::server::get::Client::new(BufReader::new(client_con)).unwrap(),
|
|
)));
|
|
}
|
|
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();
|
|
#[cfg(feature = "playback")]
|
|
if let Some(player) = &mut player {
|
|
player.handle_command(&update);
|
|
}
|
|
database.lock().unwrap().apply_command(update);
|
|
#[cfg(feature = "speedy2d")]
|
|
if let Some(v) = &*update_gui_sender.lock().unwrap() {
|
|
v.send_event(GuiEvent::Refresh).unwrap();
|
|
}
|
|
}
|
|
})
|
|
};
|
|
match mode {
|
|
#[cfg(feature = "speedy2d")]
|
|
Mode::Gui => {
|
|
{
|
|
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,
|
|
musicdb_lib::server::get::Client::new(BufReader::new(
|
|
TcpStream::connect(addr).expect("opening get client connection"),
|
|
))
|
|
.expect("initializing get client connection"),
|
|
sender,
|
|
)
|
|
};
|
|
}
|
|
#[cfg(feature = "playback")]
|
|
Mode::SyncplayerLocal { .. } | Mode::SyncplayerNetwork => {
|
|
con_thread.join().unwrap();
|
|
}
|
|
#[cfg(feature = "mers")]
|
|
Mode::RunMers { path } => {
|
|
let mut src = 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(
|
|
mers_lib::prelude_compile::Config::new().bundle_std(),
|
|
&database,
|
|
&Arc::new(move |cmd: Command| cmd.to_bytes(&mut *con.lock().unwrap()).unwrap()),
|
|
)
|
|
.infos();
|
|
let program = mers_lib::prelude_compile::parse(&mut src, &srca)
|
|
.unwrap()
|
|
.compile(&mut i1, mers_lib::prelude_compile::CompInfo::default())
|
|
.unwrap();
|
|
match program.check(&mut i3, None) {
|
|
Ok(_) => {}
|
|
Err(e) => {
|
|
eprintln!("{e}");
|
|
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;
|
|
}
|
|
}
|
|
program.run(&mut i2);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn accumulate<F: FnMut() -> Option<T>, T>(mut f: F) -> Vec<T> {
|
|
let mut o = vec![];
|
|
loop {
|
|
if let Some(v) = f() {
|
|
o.push(v);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
o
|
|
}
|
|
|
|
fn get_cover(song: SongId, database: &Database) -> Option<CoverId> {
|
|
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<f32>) -> Color {
|
|
Color::from_rgba(c.r() * r, c.g() * g, c.b() * b, new_alpha.unwrap_or(c.a()))
|
|
}
|