mirror of
https://github.com/Dummi26/musicdb.git
synced 2025-03-10 14:13:53 +01:00
lib_dir is no longer saved in dbfile
This commit is contained in:
parent
4a729c596c
commit
3093ec1a25
@ -6,6 +6,7 @@ edition = "2021"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
clap = { version = "4.4.6", features = ["derive"] }
|
||||||
directories = "5.0.1"
|
directories = "5.0.1"
|
||||||
musicdb-lib = { version = "0.1.0", path = "../musicdb-lib" }
|
musicdb-lib = { version = "0.1.0", path = "../musicdb-lib" }
|
||||||
regex = "1.9.3"
|
regex = "1.9.3"
|
||||||
|
@ -198,7 +198,7 @@ impl Gui {
|
|||||||
| Command::Pause
|
| Command::Pause
|
||||||
| Command::Stop
|
| Command::Stop
|
||||||
| Command::Save
|
| Command::Save
|
||||||
| Command::SetLibraryDirectory(..) => {}
|
| Command::InitComplete => {}
|
||||||
Command::NextSong
|
Command::NextSong
|
||||||
| Command::QueueUpdate(..)
|
| Command::QueueUpdate(..)
|
||||||
| Command::QueueAdd(..)
|
| Command::QueueAdd(..)
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use std::{
|
use std::{
|
||||||
eprintln, fs,
|
|
||||||
io::{BufReader, Write},
|
io::{BufReader, Write},
|
||||||
net::{SocketAddr, TcpStream},
|
net::{SocketAddr, TcpStream},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
@ -8,15 +7,12 @@ use std::{
|
|||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
use gui::GuiEvent;
|
use gui::GuiEvent;
|
||||||
use musicdb_lib::{
|
use musicdb_lib::{
|
||||||
data::{
|
data::{
|
||||||
album::Album,
|
database::{ClientIo, Database},
|
||||||
artist::Artist,
|
CoverId, SongId,
|
||||||
database::{ClientIo, Cover, Database},
|
|
||||||
queue::QueueContent,
|
|
||||||
song::Song,
|
|
||||||
CoverId, DatabaseLocation, GeneralData, SongId,
|
|
||||||
},
|
},
|
||||||
load::ToFromBytes,
|
load::ToFromBytes,
|
||||||
player::Player,
|
player::Player,
|
||||||
@ -44,12 +40,24 @@ mod gui_text;
|
|||||||
mod gui_wrappers;
|
mod gui_wrappers;
|
||||||
mod textcfg;
|
mod textcfg;
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[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 {
|
enum Mode {
|
||||||
Cli,
|
#[cfg(feature = "speedy2d")]
|
||||||
|
/// graphical user interface
|
||||||
Gui,
|
Gui,
|
||||||
SyncPlayer,
|
/// play in sync with the server, but load the songs from a local copy of the lib-dir
|
||||||
SyncPlayerWithoutData,
|
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
|
||||||
|
SyncplayerNetwork,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_config_file_path() -> PathBuf {
|
fn get_config_file_path() -> PathBuf {
|
||||||
@ -57,36 +65,15 @@ fn get_config_file_path() -> PathBuf {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.config_dir()
|
.config_dir()
|
||||||
.to_path_buf()
|
.to_path_buf()
|
||||||
// if let Ok(config_home) = std::env::var("XDG_CONFIG_HOME") {
|
|
||||||
// let mut config_home: PathBuf = config_home.into();
|
|
||||||
// config_home.push("musicdb-client");
|
|
||||||
// config_home
|
|
||||||
// } else if let Ok(home) = std::env::var("HOME") {
|
|
||||||
// let mut config_home: PathBuf = home.into();
|
|
||||||
// config_home.push(".config");
|
|
||||||
// config_home.push("musicdb-client");
|
|
||||||
// config_home
|
|
||||||
// } else {
|
|
||||||
// eprintln!("No config directory!");
|
|
||||||
// std::process::exit(24);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut args = std::env::args().skip(1);
|
// parse args
|
||||||
let mode = match args.next().as_ref().map(|v| v.trim()) {
|
let args = Args::parse();
|
||||||
Some("cli") => Mode::Cli,
|
// start
|
||||||
Some("gui") => Mode::Gui,
|
let addr = args.addr;
|
||||||
Some("syncplayer") => Mode::SyncPlayer,
|
|
||||||
Some("syncplayernd") => Mode::SyncPlayerWithoutData,
|
|
||||||
_ => {
|
|
||||||
println!("Run with argument <cli/gui/syncplayer/syncplayernd>!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let addr = args.next().unwrap_or("127.0.0.1:26314".to_string());
|
|
||||||
let addr = addr.parse::<SocketAddr>().unwrap();
|
|
||||||
let mut con = TcpStream::connect(addr).unwrap();
|
let mut con = TcpStream::connect(addr).unwrap();
|
||||||
|
let mode = args.mode;
|
||||||
writeln!(con, "main").unwrap();
|
writeln!(con, "main").unwrap();
|
||||||
let database = Arc::new(Mutex::new(Database::new_clientside()));
|
let database = Arc::new(Mutex::new(Database::new_clientside()));
|
||||||
#[cfg(feature = "speedy2d")]
|
#[cfg(feature = "speedy2d")]
|
||||||
@ -95,16 +82,21 @@ fn main() {
|
|||||||
#[cfg(feature = "speedy2d")]
|
#[cfg(feature = "speedy2d")]
|
||||||
let sender = Arc::clone(&update_gui_sender);
|
let sender = Arc::clone(&update_gui_sender);
|
||||||
let con_thread = {
|
let con_thread = {
|
||||||
|
let mode = mode.clone();
|
||||||
let database = Arc::clone(&database);
|
let database = Arc::clone(&database);
|
||||||
let mut con = con.try_clone().unwrap();
|
let mut con = con.try_clone().unwrap();
|
||||||
// this is all you need to keep the db in sync
|
// this is all you need to keep the db in sync
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
let mut player = if matches!(mode, Mode::SyncPlayer | Mode::SyncPlayerWithoutData) {
|
let mut player =
|
||||||
|
if matches!(mode, Mode::SyncplayerLocal { .. } | Mode::SyncplayerNetwork) {
|
||||||
Some(Player::new().unwrap())
|
Some(Player::new().unwrap())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
if matches!(mode, Mode::SyncPlayerWithoutData) {
|
if let Mode::SyncplayerLocal { lib_dir } = mode {
|
||||||
|
let mut db = database.lock().unwrap();
|
||||||
|
db.lib_directory = lib_dir;
|
||||||
|
} else {
|
||||||
let mut db = database.lock().unwrap();
|
let mut db = database.lock().unwrap();
|
||||||
let client_con: Box<dyn ClientIo> = Box::new(TcpStream::connect(addr).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(
|
db.remote_server_as_song_file_source = Some(Arc::new(Mutex::new(
|
||||||
@ -114,7 +106,7 @@ fn main() {
|
|||||||
loop {
|
loop {
|
||||||
if let Some(player) = &mut player {
|
if let Some(player) = &mut player {
|
||||||
let mut db = database.lock().unwrap();
|
let mut db = database.lock().unwrap();
|
||||||
if !db.lib_directory.as_os_str().is_empty() {
|
if db.is_client_init() {
|
||||||
player.update(&mut db);
|
player.update(&mut db);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,15 +123,8 @@ fn main() {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
match mode {
|
match mode {
|
||||||
Mode::Cli => {
|
|
||||||
Looper {
|
|
||||||
con: &mut con,
|
|
||||||
database: &database,
|
|
||||||
}
|
|
||||||
.cmd_loop();
|
|
||||||
}
|
|
||||||
Mode::Gui => {
|
|
||||||
#[cfg(feature = "speedy2d")]
|
#[cfg(feature = "speedy2d")]
|
||||||
|
Mode::Gui => {
|
||||||
{
|
{
|
||||||
let occasional_refresh_sender = Arc::clone(&sender);
|
let occasional_refresh_sender = Arc::clone(&sender);
|
||||||
thread::spawn(move || loop {
|
thread::spawn(move || loop {
|
||||||
@ -159,162 +144,12 @@ fn main() {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Mode::SyncPlayer | Mode::SyncPlayerWithoutData => {
|
Mode::SyncplayerLocal { .. } | Mode::SyncplayerNetwork => {
|
||||||
con_thread.join().unwrap();
|
con_thread.join().unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
struct Looper<'a> {
|
|
||||||
pub con: &'a mut TcpStream,
|
|
||||||
pub database: &'a Arc<Mutex<Database>>,
|
|
||||||
}
|
|
||||||
impl<'a> Looper<'a> {
|
|
||||||
pub fn cmd_loop(&mut self) {
|
|
||||||
loop {
|
|
||||||
println!();
|
|
||||||
let line = self.read_line(" > enter a command (help for help)");
|
|
||||||
let line = line.trim();
|
|
||||||
match line {
|
|
||||||
"resume" => Command::Resume,
|
|
||||||
"pause" => Command::Pause,
|
|
||||||
"stop" => Command::Stop,
|
|
||||||
"next" => Command::NextSong,
|
|
||||||
"set-lib-dir" => {
|
|
||||||
let line = self.read_line("Enter the new (absolute) library directory, or leave empty to abort");
|
|
||||||
if !line.is_empty() {
|
|
||||||
Command::SetLibraryDirectory(line.into())
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"add-song" => {
|
|
||||||
let song = Song {
|
|
||||||
id: 0,
|
|
||||||
location: self.read_line("The songs file is located, relative to the library root, at...").into(),
|
|
||||||
title: self.read_line("The songs title is..."),
|
|
||||||
album: self.read_line_ido("The song is part of the album with the id... (empty for None)"),
|
|
||||||
artist: self.read_line_id("The song is made by the artist with the id..."),
|
|
||||||
more_artists: accumulate(|| self.read_line_ido("The song is made with support by other artist, one of which has the id... (will ask repeatedly; leave empty once done)")),
|
|
||||||
cover: self.read_line_ido("The song should use the cover with the id... (empty for None - will default to album or artist cover, if available)"),
|
|
||||||
general: GeneralData::default(),
|
|
||||||
cached_data: Arc::new(Mutex::new(None)),
|
|
||||||
};
|
|
||||||
println!("You are about to add the following song to the database:");
|
|
||||||
println!(" + {song}");
|
|
||||||
if self.read_line("Are you sure? (type 'yes' to continue)").to_lowercase().trim() == "yes" {
|
|
||||||
Command::AddSong(song)
|
|
||||||
} else {
|
|
||||||
println!("[-] Aborted - no event will be sent to the database.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"update-song" => {
|
|
||||||
let song_id = self.read_line_id("The ID of the song is...");
|
|
||||||
if let Some(mut song) = self.database.lock().unwrap().get_song(&song_id).cloned() {
|
|
||||||
println!("You are now editing the song {song}.");
|
|
||||||
loop {
|
|
||||||
match self.read_line("What do you want to edit? (title/album/artist/location or done)").to_lowercase().trim() {
|
|
||||||
"done" => break,
|
|
||||||
"title" => {
|
|
||||||
println!("prev: '{}'", song.title);
|
|
||||||
song.title = self.read_line("");
|
|
||||||
}
|
|
||||||
"album" => {
|
|
||||||
println!("prev: '{}'", song.album.map_or(String::new(), |v| v.to_string()));
|
|
||||||
song.album = self.read_line_ido("");
|
|
||||||
}
|
|
||||||
"artist" => {
|
|
||||||
println!("prev: '{}'", song.artist);
|
|
||||||
song.artist = self.read_line_id("");
|
|
||||||
}
|
|
||||||
"location" => {
|
|
||||||
println!("prev: '{:?}'", song.location);
|
|
||||||
song.location = self.read_line("").into();
|
|
||||||
}
|
|
||||||
_ => println!("[-] must be title/album/artist/location or done"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println!("You are about to update the song:");
|
|
||||||
println!(" + {song}");
|
|
||||||
if self.read_line("Are you sure? (type 'yes' to continue)").to_lowercase().trim() == "yes" {
|
|
||||||
Command::ModifySong(song)
|
|
||||||
} else {
|
|
||||||
println!("[-] Aborted - no event will be sent to the database.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
println!("[-] No song with that ID found, aborting.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"queue-clear" => Command::QueueUpdate(vec![], QueueContent::Folder(0, vec![], String::new()).into()),
|
|
||||||
"queue-add-to-end" => Command::QueueAdd(vec![], QueueContent::Song(self.read_line_id("The ID of the song that should be added to the end of the queue is...")).into()),
|
|
||||||
"save" => Command::Save,
|
|
||||||
"status" => {
|
|
||||||
let db = self.database.lock().unwrap();
|
|
||||||
println!("DB contains {} songs:", db.songs().len());
|
|
||||||
for song in db.songs().values() {
|
|
||||||
println!("> [{}]: {}", song.id, song);
|
|
||||||
}
|
|
||||||
println!("Queue: {:?}, then {:?}", db.queue.get_current(), db.queue.get_next());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
"exit" => {
|
|
||||||
println!("<< goodbye");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
println!("Type 'exit' to exit, 'status' to see the db, 'resume', 'pause', 'stop', 'next', 'queue-clear', 'queue-add-to-end', 'add-song', 'add-album', 'add-artist', 'update-song', 'update-album', 'update-artist', 'set-lib-dir', or 'save' to control playback or update the db.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.to_bytes(self.con)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_line(&mut self, q: &str) -> String {
|
|
||||||
loop {
|
|
||||||
if !q.is_empty() {
|
|
||||||
println!("{q}");
|
|
||||||
}
|
|
||||||
let mut line = String::new();
|
|
||||||
std::io::stdin().read_line(&mut line).unwrap();
|
|
||||||
while line.ends_with('\n') || line.ends_with('\r') {
|
|
||||||
line.pop();
|
|
||||||
}
|
|
||||||
if line.trim() == "#" {
|
|
||||||
self.cmd_loop();
|
|
||||||
} else {
|
|
||||||
return line;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_line_id(&mut self, q: &str) -> u64 {
|
|
||||||
loop {
|
|
||||||
if let Ok(v) = self.read_line(q).trim().parse() {
|
|
||||||
return v;
|
|
||||||
} else {
|
|
||||||
println!("[-] Must be a positive integer.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn read_line_ido(&mut self, q: &str) -> Option<u64> {
|
|
||||||
loop {
|
|
||||||
let line = self.read_line(q);
|
|
||||||
let line = line.trim();
|
|
||||||
if line.is_empty() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
if let Ok(v) = line.parse() {
|
|
||||||
return Some(v);
|
|
||||||
} else {
|
|
||||||
println!("[-] Must be a positive integer or nothing for None.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn accumulate<F: FnMut() -> Option<T>, T>(mut f: F) -> Vec<T> {
|
pub fn accumulate<F: FnMut() -> Option<T>, T>(mut f: F) -> Vec<T> {
|
||||||
let mut o = vec![];
|
let mut o = vec![];
|
||||||
loop {
|
loop {
|
||||||
|
@ -38,6 +38,8 @@ pub struct Database {
|
|||||||
pub command_sender: Option<mpsc::Sender<Command>>,
|
pub command_sender: Option<mpsc::Sender<Command>>,
|
||||||
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
|
||||||
|
client_is_init: bool,
|
||||||
}
|
}
|
||||||
pub trait ClientIo: Read + Write + Send {}
|
pub trait ClientIo: Read + Write + Send {}
|
||||||
impl<T: Read + Write + Send> ClientIo for T {}
|
impl<T: Read + Write + Send> ClientIo for T {}
|
||||||
@ -60,6 +62,9 @@ impl Database {
|
|||||||
pub fn is_client(&self) -> bool {
|
pub fn is_client(&self) -> bool {
|
||||||
self.db_file.as_os_str().is_empty()
|
self.db_file.as_os_str().is_empty()
|
||||||
}
|
}
|
||||||
|
pub fn is_client_init(&self) -> bool {
|
||||||
|
self.client_is_init
|
||||||
|
}
|
||||||
pub fn get_path(&self, location: &DatabaseLocation) -> PathBuf {
|
pub fn get_path(&self, location: &DatabaseLocation) -> PathBuf {
|
||||||
self.lib_directory.join(&location.rel_path)
|
self.lib_directory.join(&location.rel_path)
|
||||||
}
|
}
|
||||||
@ -220,7 +225,7 @@ impl Database {
|
|||||||
}
|
}
|
||||||
// since this is so easy to check for, it comes last.
|
// since this is so easy to check for, it comes last.
|
||||||
// this allows clients to find out when init_connection is done.
|
// this allows clients to find out when init_connection is done.
|
||||||
Command::SetLibraryDirectory(self.lib_directory.clone()).to_bytes(con)?;
|
Command::InitComplete.to_bytes(con)?;
|
||||||
// is initialized now - client can receive updates after this point.
|
// is initialized now - client can receive updates after this point.
|
||||||
// NOTE: Don't write to connection anymore - the db will dispatch updates on its own.
|
// NOTE: Don't write to connection anymore - the db will dispatch updates on its own.
|
||||||
// we just need to handle commands (receive from the connection).
|
// we just need to handle commands (receive from the connection).
|
||||||
@ -341,8 +346,8 @@ impl Database {
|
|||||||
Command::RemoveArtist(artist) => {
|
Command::RemoveArtist(artist) => {
|
||||||
_ = self.remove_artist(artist);
|
_ = self.remove_artist(artist);
|
||||||
}
|
}
|
||||||
Command::SetLibraryDirectory(new_dir) => {
|
Command::InitComplete => {
|
||||||
self.lib_directory = new_dir;
|
self.client_is_init = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -368,6 +373,7 @@ impl Database {
|
|||||||
playing: false,
|
playing: false,
|
||||||
command_sender: None,
|
command_sender: None,
|
||||||
remote_server_as_song_file_source: None,
|
remote_server_as_song_file_source: None,
|
||||||
|
client_is_init: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn new_empty(path: PathBuf, lib_dir: PathBuf) -> Self {
|
pub fn new_empty(path: PathBuf, lib_dir: PathBuf) -> Self {
|
||||||
@ -385,13 +391,12 @@ impl Database {
|
|||||||
playing: false,
|
playing: false,
|
||||||
command_sender: None,
|
command_sender: None,
|
||||||
remote_server_as_song_file_source: None,
|
remote_server_as_song_file_source: None,
|
||||||
|
client_is_init: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn load_database(path: PathBuf) -> Result<Self, std::io::Error> {
|
pub fn load_database(path: PathBuf, lib_directory: PathBuf) -> Result<Self, std::io::Error> {
|
||||||
let mut file = BufReader::new(File::open(&path)?);
|
let mut file = BufReader::new(File::open(&path)?);
|
||||||
eprintln!("[info] loading library from {file:?}");
|
eprintln!("[info] loading library from {file:?}");
|
||||||
let lib_directory = ToFromBytes::from_bytes(&mut file)?;
|
|
||||||
eprintln!("[info] library directory is {lib_directory:?}");
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
db_file: path,
|
db_file: path,
|
||||||
lib_directory,
|
lib_directory,
|
||||||
@ -406,6 +411,7 @@ impl Database {
|
|||||||
playing: false,
|
playing: false,
|
||||||
command_sender: None,
|
command_sender: None,
|
||||||
remote_server_as_song_file_source: None,
|
remote_server_as_song_file_source: None,
|
||||||
|
client_is_init: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/// saves the database's contents. save path can be overridden
|
/// saves the database's contents. save path can be overridden
|
||||||
@ -425,7 +431,6 @@ impl Database {
|
|||||||
.truncate(true)
|
.truncate(true)
|
||||||
.create(true)
|
.create(true)
|
||||||
.open(&path)?;
|
.open(&path)?;
|
||||||
self.lib_directory.to_bytes(&mut file)?;
|
|
||||||
self.artists.to_bytes(&mut file)?;
|
self.artists.to_bytes(&mut file)?;
|
||||||
self.albums.to_bytes(&mut file)?;
|
self.albums.to_bytes(&mut file)?;
|
||||||
self.songs.to_bytes(&mut file)?;
|
self.songs.to_bytes(&mut file)?;
|
||||||
@ -433,6 +438,10 @@ impl Database {
|
|||||||
Ok(path)
|
Ok(path)
|
||||||
}
|
}
|
||||||
pub fn broadcast_update(&mut self, update: &Command) {
|
pub fn broadcast_update(&mut self, update: &Command) {
|
||||||
|
match update {
|
||||||
|
Command::InitComplete => return,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
let mut remove = vec![];
|
let mut remove = vec![];
|
||||||
let mut bytes = None;
|
let mut bytes = None;
|
||||||
let mut arc = None;
|
let mut arc = None;
|
||||||
|
@ -4,7 +4,6 @@ use std::{
|
|||||||
eprintln,
|
eprintln,
|
||||||
io::{BufRead, BufReader, Read, Write},
|
io::{BufRead, BufReader, Read, Write},
|
||||||
net::{SocketAddr, TcpListener},
|
net::{SocketAddr, TcpListener},
|
||||||
path::PathBuf,
|
|
||||||
sync::{mpsc, Arc, Mutex},
|
sync::{mpsc, Arc, Mutex},
|
||||||
thread,
|
thread,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
@ -51,7 +50,7 @@ pub enum Command {
|
|||||||
RemoveAlbum(AlbumId),
|
RemoveAlbum(AlbumId),
|
||||||
RemoveArtist(ArtistId),
|
RemoveArtist(ArtistId),
|
||||||
ModifyArtist(Artist),
|
ModifyArtist(Artist),
|
||||||
SetLibraryDirectory(PathBuf),
|
InitComplete,
|
||||||
}
|
}
|
||||||
impl Command {
|
impl Command {
|
||||||
pub fn send_to_server(self, db: &Database) -> Result<(), Self> {
|
pub fn send_to_server(self, db: &Database) -> Result<(), Self> {
|
||||||
@ -277,9 +276,8 @@ impl ToFromBytes for Command {
|
|||||||
s.write_all(&[0b11011100])?;
|
s.write_all(&[0b11011100])?;
|
||||||
artist.to_bytes(s)?;
|
artist.to_bytes(s)?;
|
||||||
}
|
}
|
||||||
Self::SetLibraryDirectory(path) => {
|
Self::InitComplete => {
|
||||||
s.write_all(&[0b00110001])?;
|
s.write_all(&[0b00110001])?;
|
||||||
path.to_bytes(s)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -325,7 +323,7 @@ impl ToFromBytes for Command {
|
|||||||
0b11010011 => Self::RemoveAlbum(ToFromBytes::from_bytes(s)?),
|
0b11010011 => Self::RemoveAlbum(ToFromBytes::from_bytes(s)?),
|
||||||
0b11011100 => Self::RemoveArtist(ToFromBytes::from_bytes(s)?),
|
0b11011100 => Self::RemoveArtist(ToFromBytes::from_bytes(s)?),
|
||||||
0b01011101 => Self::AddCover(ToFromBytes::from_bytes(s)?),
|
0b01011101 => Self::AddCover(ToFromBytes::from_bytes(s)?),
|
||||||
0b00110001 => Self::SetLibraryDirectory(ToFromBytes::from_bytes(s)?),
|
0b00110001 => Self::InitComplete,
|
||||||
_ => {
|
_ => {
|
||||||
eprintln!("unexpected byte when reading command; stopping playback.");
|
eprintln!("unexpected byte when reading command; stopping playback.");
|
||||||
Self::Stop
|
Self::Stop
|
||||||
|
@ -7,6 +7,7 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { version = "0.6.19", features = ["headers"] }
|
axum = { version = "0.6.19", features = ["headers"] }
|
||||||
|
clap = { version = "4.4.6", features = ["derive"] }
|
||||||
futures = "0.3.28"
|
futures = "0.3.28"
|
||||||
headers = "0.3.8"
|
headers = "0.3.8"
|
||||||
musicdb-lib = { version = "0.1.0", path = "../musicdb-lib" }
|
musicdb-lib = { version = "0.1.0", path = "../musicdb-lib" }
|
||||||
|
@ -1,166 +1,68 @@
|
|||||||
mod web;
|
mod web;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
|
net::SocketAddr,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
process::exit,
|
process::exit,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
thread,
|
thread,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
use musicdb_lib::server::run_server;
|
use musicdb_lib::server::run_server;
|
||||||
|
|
||||||
use musicdb_lib::data::database::Database;
|
use musicdb_lib::data::database::Database;
|
||||||
|
|
||||||
/*
|
#[derive(Parser, Debug)]
|
||||||
|
struct Args {
|
||||||
# Exit codes
|
/// The file which contains information about the songs in your library
|
||||||
|
#[arg()]
|
||||||
0 => exited as requested by the user
|
dbfile: PathBuf,
|
||||||
1 => exit after printing help message
|
/// The path containing your actual library.
|
||||||
3 => error parsing cli arguments
|
#[arg()]
|
||||||
10 => tried to start with a path that caused some io::Error
|
lib_dir: PathBuf,
|
||||||
11 => tried to start with a path that does not exist (--init prevents this)
|
/// skip reading the `dbfile` (because it doesn't exist yet)
|
||||||
|
#[arg(long)]
|
||||||
*/
|
init: bool,
|
||||||
|
/// optional address for tcp connections to the server
|
||||||
|
#[arg(long)]
|
||||||
|
tcp: Option<SocketAddr>,
|
||||||
|
/// optional address on which to start a website which can be used on devices without `musicdb-client` to control playback.
|
||||||
|
/// requires the `assets/` folder to be present!
|
||||||
|
#[arg(long)]
|
||||||
|
web: Option<SocketAddr>,
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
// parse args
|
// parse args
|
||||||
let mut args = std::env::args().skip(1);
|
let args = Args::parse();
|
||||||
let mut tcp_addr = None;
|
let database = if args.init {
|
||||||
let mut web_addr = None;
|
Database::new_empty(args.dbfile, args.lib_dir)
|
||||||
let mut lib_dir_for_init = None;
|
|
||||||
let database = if let Some(path_s) = args.next() {
|
|
||||||
loop {
|
|
||||||
if let Some(arg) = args.next() {
|
|
||||||
if arg.starts_with("--") {
|
|
||||||
match &arg[2..] {
|
|
||||||
"init" => {
|
|
||||||
if let Some(lib_dir) = args.next() {
|
|
||||||
lib_dir_for_init = Some(lib_dir);
|
|
||||||
} else {
|
} else {
|
||||||
eprintln!(
|
match Database::load_database(args.dbfile.clone(), args.lib_dir.clone()) {
|
||||||
"[EXIT]
|
Ok(db) => db,
|
||||||
missing argument: --init <lib path>"
|
|
||||||
);
|
|
||||||
exit(3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"tcp" => {
|
|
||||||
if let Some(addr) = args.next() {
|
|
||||||
if let Ok(addr) = addr.parse() {
|
|
||||||
tcp_addr = Some(addr)
|
|
||||||
} else {
|
|
||||||
eprintln!(
|
|
||||||
"[EXIT]
|
|
||||||
bad argument: --tcp <addr:port>: couldn't parse <addr:port>"
|
|
||||||
);
|
|
||||||
exit(3);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
eprintln!(
|
|
||||||
"[EXIT]
|
|
||||||
missing argument: --tcp <addr:port>"
|
|
||||||
);
|
|
||||||
exit(3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"web" => {
|
|
||||||
if let Some(addr) = args.next() {
|
|
||||||
if let Ok(addr) = addr.parse() {
|
|
||||||
web_addr = Some(addr)
|
|
||||||
} else {
|
|
||||||
eprintln!(
|
|
||||||
"[EXIT]
|
|
||||||
bad argument: --web <addr:port>: couldn't parse <addr:port>"
|
|
||||||
);
|
|
||||||
exit(3);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
eprintln!(
|
|
||||||
"[EXIT]
|
|
||||||
missing argument: --web <addr:port>"
|
|
||||||
);
|
|
||||||
exit(3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
o => {
|
|
||||||
eprintln!(
|
|
||||||
"[EXIT]
|
|
||||||
Unknown long argument --{o}"
|
|
||||||
);
|
|
||||||
exit(3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if arg.starts_with("-") {
|
|
||||||
match &arg[1..] {
|
|
||||||
o => {
|
|
||||||
eprintln!(
|
|
||||||
"[EXIT]
|
|
||||||
Unknown short argument -{o}"
|
|
||||||
);
|
|
||||||
exit(3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
eprintln!(
|
|
||||||
"[EXIT]
|
|
||||||
Argument didn't start with - or -- ({arg})."
|
|
||||||
);
|
|
||||||
exit(3);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let path = PathBuf::from(&path_s);
|
|
||||||
match path.try_exists() {
|
|
||||||
Ok(exists) => {
|
|
||||||
if let Some(lib_directory) = lib_dir_for_init {
|
|
||||||
Database::new_empty(path, lib_directory.into())
|
|
||||||
} else if exists {
|
|
||||||
Database::load_database(path).unwrap()
|
|
||||||
} else {
|
|
||||||
eprintln!(
|
|
||||||
"[EXIT]
|
|
||||||
The provided path does not exist."
|
|
||||||
);
|
|
||||||
exit(11);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!(
|
eprintln!("Couldn't load database!");
|
||||||
"[EXIT]
|
eprintln!(" dbfile: {:?}", args.dbfile);
|
||||||
Error getting information about the provided path '{path_s}': {e}"
|
eprintln!(" libdir: {:?}", args.lib_dir);
|
||||||
);
|
eprintln!(" err: {}", e);
|
||||||
exit(10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
eprintln!(
|
|
||||||
"[EXIT]
|
|
||||||
musicdb-server - help
|
|
||||||
musicdb-server <path to database file> <options> <options> <...>
|
|
||||||
options:
|
|
||||||
--init <lib directory>
|
|
||||||
--tcp <addr:port>
|
|
||||||
--web <addr:port>
|
|
||||||
this help was shown because no arguments were provided."
|
|
||||||
);
|
|
||||||
exit(1);
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// database can be shared by multiple threads using Arc<Mutex<_>>
|
// database can be shared by multiple threads using Arc<Mutex<_>>
|
||||||
let database = Arc::new(Mutex::new(database));
|
let database = Arc::new(Mutex::new(database));
|
||||||
if tcp_addr.is_some() || web_addr.is_some() {
|
if args.tcp.is_some() || args.web.is_some() {
|
||||||
if let Some(addr) = web_addr {
|
if let Some(addr) = &args.web {
|
||||||
let (s, mut r) = tokio::sync::mpsc::channel(2);
|
let (s, mut r) = tokio::sync::mpsc::channel(2);
|
||||||
let db = Arc::clone(&database);
|
let db = Arc::clone(&database);
|
||||||
thread::spawn(move || run_server(database, tcp_addr, Some(s)));
|
thread::spawn(move || run_server(database, args.tcp, Some(s)));
|
||||||
if let Some(sender) = r.recv().await {
|
if let Some(sender) = r.recv().await {
|
||||||
web::main(db, sender, addr).await;
|
web::main(db, sender, *addr).await;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
run_server(database, tcp_addr, None);
|
run_server(database, args.tcp, None);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
eprintln!("nothing to do, not starting the server.");
|
eprintln!("nothing to do, not starting the server.");
|
||||||
|
@ -438,7 +438,7 @@ async fn sse_handler(
|
|||||||
.collect::<String>(),
|
.collect::<String>(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Command::Save | Command::SetLibraryDirectory(_) => return Poll::Pending,
|
Command::Save | Command::InitComplete => return Poll::Pending,
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
return Poll::Pending;
|
return Poll::Pending;
|
||||||
@ -673,8 +673,7 @@ fn build_queue_content_build(
|
|||||||
HtmlPart::Plain(v) => html.push_str(v),
|
HtmlPart::Plain(v) => html.push_str(v),
|
||||||
HtmlPart::Insert(key) => match key.as_str() {
|
HtmlPart::Insert(key) => match key.as_str() {
|
||||||
"path" => html.push_str(&path),
|
"path" => html.push_str(&path),
|
||||||
"content" => {
|
"content" => build_queue_content_build(
|
||||||
build_queue_content_build(
|
|
||||||
db,
|
db,
|
||||||
state,
|
state,
|
||||||
html,
|
html,
|
||||||
@ -682,8 +681,7 @@ fn build_queue_content_build(
|
|||||||
format!("{path}-0"),
|
format!("{path}-0"),
|
||||||
current,
|
current,
|
||||||
true,
|
true,
|
||||||
)
|
),
|
||||||
}
|
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user