small improvements idk i forgot i had a git repo for this project

This commit is contained in:
Mark
2023-08-24 16:15:01 +02:00
parent 0ae0126f04
commit 9fbe67012e
17 changed files with 1894 additions and 455 deletions

View File

@@ -9,6 +9,7 @@ edition = "2021"
musicdb-lib = { version = "0.1.0", path = "../musicdb-lib" }
regex = "1.9.3"
speedy2d = { version = "1.12.0", optional = true }
toml = "0.7.6"
[features]
default = ["speedy2d"]

View File

@@ -1,6 +1,7 @@
use std::{
any::Any,
eprintln,
io::{Read, Write},
net::TcpStream,
sync::{Arc, Mutex},
time::Instant,
@@ -10,7 +11,7 @@ use std::{
use musicdb_lib::{
data::{database::Database, queue::Queue, AlbumId, ArtistId, SongId},
load::ToFromBytes,
server::Command,
server::{get, Command},
};
use speedy2d::{
color::Color,
@@ -33,11 +34,74 @@ pub enum GuiEvent {
Exit,
}
pub fn main(
pub fn main<T: Write + Read + 'static + Sync + Send>(
database: Arc<Mutex<Database>>,
connection: TcpStream,
get_con: get::Client<T>,
event_sender_arc: Arc<Mutex<Option<UserEventSender<GuiEvent>>>>,
) {
let mut config_file = super::get_config_file_path();
config_file.push("config_gui.toml");
let mut font = None;
let mut line_height = 32.0;
let mut scroll_pixels_multiplier = 1.0;
let mut scroll_lines_multiplier = 3.0;
let mut scroll_pages_multiplier = 0.75;
match std::fs::read_to_string(&config_file) {
Ok(cfg) => {
if let Ok(table) = cfg.parse::<toml::Table>() {
if let Some(path) = table["font"].as_str() {
if let Ok(bytes) = std::fs::read(path) {
if let Ok(f) = Font::new(&bytes) {
font = Some(f);
} else {
eprintln!("[toml] couldn't load font")
}
} else {
eprintln!("[toml] couldn't read font file")
}
}
if let Some(v) = table.get("line_height").and_then(|v| v.as_float()) {
line_height = v as _;
}
if let Some(v) = table
.get("scroll_pixels_multiplier")
.and_then(|v| v.as_float())
{
scroll_pixels_multiplier = v;
}
if let Some(v) = table
.get("scroll_lines_multiplier")
.and_then(|v| v.as_float())
{
scroll_lines_multiplier = v;
}
if let Some(v) = table
.get("scroll_pages_multiplier")
.and_then(|v| v.as_float())
{
scroll_pages_multiplier = v;
}
} else {
eprintln!("Couldn't parse config file {config_file:?} as toml!");
}
}
Err(e) => {
eprintln!("[exit] no config file found at {config_file:?}: {e}");
if let Some(p) = config_file.parent() {
_ = std::fs::create_dir_all(p);
}
_ = std::fs::write(&config_file, "font = \"\"");
std::process::exit(25);
}
}
let font = if let Some(v) = font {
v
} else {
eprintln!("[toml] required: font = <string>");
std::process::exit(30);
};
let window = speedy2d::Window::<GuiEvent>::new_with_user_events(
"MusicDB Client",
WindowCreationOptions::new_fullscreen_borderless(),
@@ -45,7 +109,18 @@ pub fn main(
.expect("couldn't open window");
*event_sender_arc.lock().unwrap() = Some(window.create_user_event_sender());
let sender = window.create_user_event_sender();
window.run_loop(Gui::new(database, connection, event_sender_arc, sender));
window.run_loop(Gui::new(
font,
database,
connection,
get_con,
event_sender_arc,
sender,
line_height,
scroll_pixels_multiplier,
scroll_lines_multiplier,
scroll_pages_multiplier,
));
}
pub struct Gui {
@@ -69,11 +144,17 @@ pub struct Gui {
pub scroll_pages_multiplier: f64,
}
impl Gui {
fn new(
fn new<T: Read + Write + 'static + Sync + Send>(
font: Font,
database: Arc<Mutex<Database>>,
connection: TcpStream,
get_con: get::Client<T>,
event_sender_arc: Arc<Mutex<Option<UserEventSender<GuiEvent>>>>,
event_sender: UserEventSender<GuiEvent>,
line_height: f32,
scroll_pixels_multiplier: f64,
scroll_lines_multiplier: f64,
scroll_pages_multiplier: f64,
) -> Self {
database.lock().unwrap().update_endpoints.push(
musicdb_lib::data::database::UpdateEndpoint::Custom(Box::new(move |cmd| match cmd {
@@ -87,7 +168,8 @@ impl Gui {
| Command::QueueAdd(..)
| Command::QueueInsert(..)
| Command::QueueRemove(..)
| Command::QueueGoto(..) => {
| Command::QueueGoto(..)
| Command::QueueSetShuffle(..) => {
if let Some(s) = &*event_sender_arc.lock().unwrap() {
_ = s.send_event(GuiEvent::UpdatedQueue);
}
@@ -96,6 +178,7 @@ impl Gui {
| Command::AddSong(_)
| Command::AddAlbum(_)
| Command::AddArtist(_)
| Command::AddCover(_)
| Command::ModifySong(_)
| Command::ModifyAlbum(_)
| Command::ModifyArtist(_) => {
@@ -105,10 +188,6 @@ impl Gui {
}
})),
);
let line_height = 32.0;
let scroll_pixels_multiplier = 1.0;
let scroll_lines_multiplier = 3.0;
let scroll_pages_multiplier = 0.75;
Gui {
event_sender,
database,
@@ -117,6 +196,7 @@ impl Gui {
VirtualKeyCode::Escape,
GuiScreen::new(
GuiElemCfg::default(),
get_con,
line_height,
scroll_pixels_multiplier,
scroll_lines_multiplier,
@@ -125,10 +205,7 @@ impl Gui {
)),
size: UVec2::ZERO,
mouse_pos: Vec2::ZERO,
font: Font::new(include_bytes!(
"/usr/share/fonts/mozilla-fira/FiraSans-Regular.otf"
))
.unwrap(),
font,
// font: Font::new(include_bytes!("/usr/share/fonts/TTF/FiraSans-Regular.ttf")).unwrap(),
last_draw: Instant::now(),
modifiers: ModifiersState::default(),
@@ -328,6 +405,10 @@ pub struct DrawInfo<'a> {
pub child_has_keyboard_focus: bool,
/// the height of one line of text (in pixels)
pub line_height: f32,
pub dragging: Option<(
Dragging,
Option<Box<dyn FnMut(&mut DrawInfo, &mut Graphics2D)>>,
)>,
}
/// Generic wrapper over anything that implements GuiElemTrait
@@ -679,9 +760,11 @@ impl WindowHandler<GuiEvent> for Gui {
has_keyboard_focus: false,
child_has_keyboard_focus: true,
line_height: self.line_height,
dragging: self.dragging.take(),
};
self.gui.draw(&mut info, graphics);
let actions = std::mem::replace(&mut info.actions, Vec::with_capacity(0));
self.dragging = info.dragging.take();
if let Some((d, f)) = &mut self.dragging {
if let Some(f) = f {
f(&mut info, graphics);
@@ -753,12 +836,15 @@ impl WindowHandler<GuiEvent> for Gui {
distance: speedy2d::window::MouseScrollDistance,
) {
let dist = match distance {
MouseScrollDistance::Pixels { y, .. } => (self.scroll_pixels_multiplier * y) as f32,
MouseScrollDistance::Pixels { y, .. } => {
(self.scroll_pixels_multiplier * y * self.scroll_lines_multiplier) as f32
}
MouseScrollDistance::Lines { y, .. } => {
(self.scroll_lines_multiplier * y) as f32 * self.line_height
}
MouseScrollDistance::Pages { y, .. } => {
(self.scroll_pages_multiplier * y) as f32 * self.last_height
(self.scroll_pages_multiplier * y * self.scroll_lines_multiplier) as f32
* self.last_height
}
};
if let Some(a) = self.gui.mouse_wheel(dist, self.mouse_pos.clone()) {

View File

@@ -63,6 +63,55 @@ impl GuiElemTrait for Panel {
}
}
#[derive(Clone)]
pub struct Square {
config: GuiElemCfg,
pub inner: GuiElem,
}
impl Square {
pub fn new(mut config: GuiElemCfg, inner: GuiElem) -> Self {
config.redraw = true;
Self { config, inner }
}
}
impl GuiElemTrait for Square {
fn config(&self) -> &GuiElemCfg {
&self.config
}
fn config_mut(&mut self) -> &mut GuiElemCfg {
&mut self.config
}
fn children(&mut self) -> Box<dyn Iterator<Item = &mut GuiElem> + '_> {
Box::new([&mut self.inner].into_iter())
}
fn any(&self) -> &dyn std::any::Any {
self
}
fn any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn clone_gui(&self) -> Box<dyn GuiElemTrait> {
Box::new(self.clone())
}
fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) {
if info.pos.size() != self.config.pixel_pos.size() {
self.config.redraw = true;
}
if self.config.redraw {
self.config.redraw = false;
if info.pos.width() > info.pos.height() {
let w = 0.5 * info.pos.height() / info.pos.width();
self.inner.inner.config_mut().pos =
Rectangle::from_tuples((0.5 - w, 0.0), (0.5 + w, 1.0));
} else {
let h = 0.5 * info.pos.width() / info.pos.height();
self.inner.inner.config_mut().pos =
Rectangle::from_tuples((0.0, 0.5 - h), (1.0, 0.5 + h));
}
}
}
}
#[derive(Clone)]
pub struct ScrollBox {
config: GuiElemCfg,

View File

@@ -189,6 +189,28 @@ impl LibraryBrowser {
)),
artist_height,
));
for song_id in &artist.singles {
if let Some(song) = db.songs().get(song_id) {
if self.search_song.is_empty()
|| self
.search_song_regex
.as_ref()
.is_some_and(|regex| regex.is_match(&song.title))
{
if let Some(g) = artist_gui.take() {
gui_elements.push(g);
}
gui_elements.push((
GuiElem::new(ListSong::new(
GuiElemCfg::default(),
*song_id,
song.title.clone(),
)),
song_height,
));
}
}
}
for album_id in &artist.albums {
if let Some(album) = db.albums().get(album_id) {
if self.search_album.is_empty()

View File

@@ -1,8 +1,15 @@
use musicdb_lib::{
data::{queue::QueueContent, SongId},
server::Command,
use std::{
io::{Cursor, Read, Write},
thread::{self, JoinHandle},
};
use musicdb_lib::{
data::{queue::QueueContent, CoverId, SongId},
server::{get, Command},
};
use speedy2d::{
color::Color, dimen::Vec2, image::ImageHandle, shape::Rectangle, window::MouseButton,
};
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, window::MouseButton};
use crate::{
gui::{adjust_area, adjust_pos, GuiAction, GuiElem, GuiElemCfg, GuiElemTrait},
@@ -16,38 +23,60 @@ This file could probably have a better name.
*/
#[derive(Clone)]
pub struct CurrentSong {
pub struct CurrentSong<T: Read + Write> {
config: GuiElemCfg,
children: Vec<GuiElem>,
get_con: Option<get::Client<T>>,
prev_song: Option<SongId>,
cover_pos: Rectangle,
cover_id: Option<CoverId>,
cover: Option<ImageHandle>,
new_cover: Option<JoinHandle<(get::Client<T>, Option<Vec<u8>>)>>,
}
impl CurrentSong {
pub fn new(config: GuiElemCfg) -> Self {
impl<T: Read + Write> Clone for CurrentSong<T> {
fn clone(&self) -> Self {
Self {
config: self.config.clone(),
children: self.children.clone(),
get_con: None,
prev_song: None,
cover_pos: self.cover_pos.clone(),
cover_id: None,
cover: None,
new_cover: None,
}
}
}
impl<T: Read + Write + 'static + Sync + Send> CurrentSong<T> {
pub fn new(config: GuiElemCfg, get_con: get::Client<T>) -> Self {
Self {
config,
children: vec![
GuiElem::new(Label::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (1.0, 0.5))),
GuiElemCfg::at(Rectangle::from_tuples((0.4, 0.0), (1.0, 0.5))),
"".to_owned(),
Color::from_int_rgb(180, 180, 210),
None,
Vec2::new(0.1, 1.0),
Vec2::new(0.0, 1.0),
)),
GuiElem::new(Label::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.5), (0.5, 1.0))),
GuiElemCfg::at(Rectangle::from_tuples((0.4, 0.5), (1.0, 1.0))),
"".to_owned(),
Color::from_int_rgb(120, 120, 120),
None,
Vec2::new(0.3, 0.0),
Vec2::new(0.0, 0.0),
)),
],
get_con: Some(get_con),
cover_pos: Rectangle::new(Vec2::ZERO, Vec2::ZERO),
cover_id: None,
prev_song: None,
cover: None,
new_cover: None,
}
}
}
impl GuiElemTrait for CurrentSong {
impl<T: Read + Write + 'static + Sync + Send> GuiElemTrait for CurrentSong<T> {
fn config(&self) -> &GuiElemCfg {
&self.config
}
@@ -67,79 +96,155 @@ impl GuiElemTrait for CurrentSong {
Box::new(self.clone())
}
fn draw(&mut self, info: &mut crate::gui::DrawInfo, g: &mut speedy2d::Graphics2D) {
let song = if let Some(v) = info.database.queue.get_current() {
if let QueueContent::Song(song) = v.content() {
if Some(*song) == self.prev_song {
// same song as before
return;
} else {
Some(*song)
}
} else if self.prev_song.is_none() {
// no song, nothing in queue
return;
} else {
// check if there is a new song
let new_song = if let Some(song) = info.database.queue.get_current_song() {
if Some(*song) == self.prev_song {
// same song as before
None
} else {
Some(Some(*song))
}
} else if self.prev_song.is_none() {
// no song, nothing in queue
return;
} else {
None
} else {
self.cover = None;
Some(None)
};
if self.prev_song != song {
self.config.redraw = true;
self.prev_song = song;
}
if self.config.redraw {
self.config.redraw = false;
let (name, subtext) = if let Some(song) = song {
if let Some(song) = info.database.get_song(&song) {
let sub = match (
song.artist
.as_ref()
.and_then(|id| info.database.artists().get(id)),
song.album
.as_ref()
.and_then(|id| info.database.albums().get(id)),
) {
(None, None) => String::new(),
(Some(artist), None) => format!("by {}", artist.name),
(None, Some(album)) => {
if let Some(artist) = album
.artist
.as_ref()
.and_then(|id| info.database.artists().get(id))
{
format!("on {} by {}", album.name, artist.name)
} else {
format!("on {}", album.name)
}
}
(Some(artist), Some(album)) => {
format!("by {} on {}", artist.name, album.name)
}
};
(song.title.clone(), sub)
} else {
(
"< song not in db >".to_owned(),
"maybe restart the client to resync the database?".to_owned(),
)
}
// drawing stuff
if self.config.pixel_pos.size() != info.pos.size() {
let leftright = 0.05;
let topbottom = 0.05;
let mut width = 0.3;
let mut height = 1.0 - topbottom * 2.0;
if width * info.pos.width() < height * info.pos.height() {
height = width * info.pos.width() / info.pos.height();
} else {
(String::new(), String::new())
};
*self.children[0]
.try_as_mut::<Label>()
.unwrap()
.content
.text() = name;
*self.children[1]
.try_as_mut::<Label>()
.unwrap()
.content
.text() = subtext;
width = height * info.pos.height() / info.pos.width();
}
let right = leftright + width + leftright;
self.cover_pos = Rectangle::from_tuples(
(leftright, 0.5 - 0.5 * height),
(leftright + width, 0.5 + 0.5 * height),
);
for el in self.children.iter_mut().take(2) {
let pos = &mut el.inner.config_mut().pos;
*pos = Rectangle::new(Vec2::new(right, pos.top_left().y), *pos.bottom_right());
}
}
if self.new_cover.as_ref().is_some_and(|v| v.is_finished()) {
let (get_con, cover) = self.new_cover.take().unwrap().join().unwrap();
self.get_con = Some(get_con);
if let Some(cover) = cover {
self.cover = g
.create_image_from_file_bytes(
None,
speedy2d::image::ImageSmoothingMode::Linear,
Cursor::new(cover),
)
.ok();
}
}
if let Some(cover) = &self.cover {
g.draw_rectangle_image(
Rectangle::new(
Vec2::new(
info.pos.top_left().x + info.pos.width() * self.cover_pos.top_left().x,
info.pos.top_left().y + info.pos.height() * self.cover_pos.top_left().y,
),
Vec2::new(
info.pos.top_left().x + info.pos.width() * self.cover_pos.bottom_right().x,
info.pos.top_left().y + info.pos.height() * self.cover_pos.bottom_right().y,
),
),
cover,
);
}
if let Some(new_song) = new_song {
// if there is a new song:
if self.prev_song != new_song {
self.config.redraw = true;
self.prev_song = new_song;
}
if self.config.redraw {
self.config.redraw = false;
let (name, subtext) = if let Some(song) = new_song {
if let Some(song) = info.database.get_song(&song) {
let cover = if let Some(v) = song.cover {
Some(v)
} else if let Some(v) = song
.album
.as_ref()
.and_then(|id| info.database.albums().get(id))
.and_then(|album| album.cover)
{
Some(v)
} else {
None
};
if cover != self.cover_id {
self.cover = None;
if let Some(cover) = cover {
if let Some(mut get_con) = self.get_con.take() {
self.new_cover = Some(thread::spawn(move || {
match get_con.cover_bytes(cover).unwrap() {
Ok(v) => (get_con, Some(v)),
Err(e) => {
eprintln!("couldn't get cover (response: {e})");
(get_con, None)
}
}
}));
}
}
self.cover_id = cover;
}
let sub = match (
song.artist
.as_ref()
.and_then(|id| info.database.artists().get(id)),
song.album
.as_ref()
.and_then(|id| info.database.albums().get(id)),
) {
(None, None) => String::new(),
(Some(artist), None) => format!("by {}", artist.name),
(None, Some(album)) => {
if let Some(artist) = album
.artist
.as_ref()
.and_then(|id| info.database.artists().get(id))
{
format!("on {} by {}", album.name, artist.name)
} else {
format!("on {}", album.name)
}
}
(Some(artist), Some(album)) => {
format!("by {} on {}", artist.name, album.name)
}
};
(song.title.clone(), sub)
} else {
(
"< song not in db >".to_owned(),
"maybe restart the client to resync the database?".to_owned(),
)
}
} else {
(String::new(), String::new())
};
*self.children[0]
.try_as_mut::<Label>()
.unwrap()
.content
.text() = name;
*self.children[1]
.try_as_mut::<Label>()
.unwrap()
.content
.text() = subtext;
}
}
}
}
@@ -230,16 +335,22 @@ impl GuiElemTrait for PlayPauseToggle {
}
}
fn mouse_pressed(&mut self, button: MouseButton) -> Vec<GuiAction> {
if !self.playing_waiting_for_change {
self.playing_target = !self.playing_target;
self.playing_waiting_for_change = true;
vec![GuiAction::SendToServer(if self.playing_target {
Command::Resume
} else {
Command::Pause
})]
} else {
vec![]
match button {
MouseButton::Left => {
if !self.playing_waiting_for_change {
self.playing_target = !self.playing_target;
self.playing_waiting_for_change = true;
vec![GuiAction::SendToServer(if self.playing_target {
Command::Resume
} else {
Command::Pause
})]
} else {
vec![]
}
}
MouseButton::Right => vec![GuiAction::SendToServer(Command::NextSong)],
_ => vec![],
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,9 @@
use std::time::{Duration, Instant};
use std::{
io::{Read, Write},
time::{Duration, Instant},
};
use musicdb_lib::server::get;
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, Graphics2D};
use crate::{
@@ -44,8 +48,9 @@ pub struct GuiScreen {
pub prev_mouse_pos: Vec2,
}
impl GuiScreen {
pub fn new(
pub fn new<T: Read + Write + 'static + Sync + Send>(
config: GuiElemCfg,
get_con: get::Client<T>,
line_height: f32,
scroll_sensitivity_pixels: f64,
scroll_sensitivity_lines: f64,
@@ -57,6 +62,7 @@ impl GuiScreen {
GuiElem::new(StatusBar::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.9), (1.0, 1.0))),
true,
get_con,
)),
GuiElem::new(Settings::new(
GuiElemCfg::default().disabled(),
@@ -69,7 +75,7 @@ impl GuiScreen {
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (1.0, 0.9))),
vec![
GuiElem::new(Button::new(
GuiElemCfg::at(Rectangle::from_tuples((0.5, 0.0), (0.75, 0.1))),
GuiElemCfg::at(Rectangle::from_tuples((0.5, 0.0), (0.75, 0.03))),
|_| vec![GuiAction::OpenSettings(true)],
vec![GuiElem::new(Label::new(
GuiElemCfg::default(),
@@ -80,7 +86,7 @@ impl GuiScreen {
))],
)),
GuiElem::new(Button::new(
GuiElemCfg::at(Rectangle::from_tuples((0.75, 0.0), (1.0, 0.1))),
GuiElemCfg::at(Rectangle::from_tuples((0.75, 0.0), (1.0, 0.03))),
|_| vec![GuiAction::Exit],
vec![GuiElem::new(Label::new(
GuiElemCfg::default(),
@@ -95,7 +101,7 @@ impl GuiScreen {
(0.5, 1.0),
)))),
GuiElem::new(QueueViewer::new(GuiElemCfg::at(Rectangle::from_tuples(
(0.5, 0.1),
(0.5, 0.03),
(1.0, 1.0),
)))),
],
@@ -199,9 +205,10 @@ impl GuiElemTrait for GuiScreen {
if !self.idle.0 || self.idle.1.is_none() {
if let Some(h) = &info.helper {
h.set_cursor_visible(!self.idle.0);
for el in self.children.iter_mut().skip(1) {
el.inner.config_mut().enabled = !self.idle.0;
if self.settings.0 {
self.children[1].inner.config_mut().enabled = !self.idle.0;
}
self.children[2].inner.config_mut().enabled = !self.idle.0;
}
}
let p = transition(p1);
@@ -241,14 +248,18 @@ pub struct StatusBar {
idle_mode: f32,
}
impl StatusBar {
pub fn new(config: GuiElemCfg, playing: bool) -> Self {
pub fn new<T: Read + Write + 'static + Sync + Send>(
config: GuiElemCfg,
playing: bool,
get_con: get::Client<T>,
) -> Self {
Self {
config,
children: vec![
GuiElem::new(CurrentSong::new(GuiElemCfg::at(Rectangle::new(
Vec2::ZERO,
Vec2::new(0.8, 1.0),
)))),
GuiElem::new(CurrentSong::new(
GuiElemCfg::at(Rectangle::new(Vec2::ZERO, Vec2::new(0.8, 1.0))),
get_con,
)),
GuiElem::new(PlayPauseToggle::new(
GuiElemCfg::at(Rectangle::from_tuples((0.85, 0.0), (0.95, 1.0))),
false,

View File

@@ -124,7 +124,7 @@ impl Settings {
(0.0, 0.0),
(0.33, 1.0),
)),
"Scroll Sensitivity (lines)".to_string(),
"Scroll Sensitivity".to_string(),
Color::WHITE,
None,
Vec2::new(0.9, 0.5),

View File

@@ -1,5 +1,6 @@
use std::{
eprintln, fs,
io::{BufReader, Write},
net::{SocketAddr, TcpStream},
path::PathBuf,
sync::{Arc, Mutex},
@@ -10,12 +11,16 @@ use std::{
use gui::GuiEvent;
use musicdb_lib::{
data::{
album::Album, artist::Artist, database::Database, queue::QueueContent, song::Song,
album::Album,
artist::Artist,
database::{Cover, Database},
queue::QueueContent,
song::Song,
DatabaseLocation, GeneralData,
},
load::ToFromBytes,
player::Player,
server::Command,
server::{get, Command},
};
#[cfg(feature = "speedy2d")]
mod gui;
@@ -43,6 +48,22 @@ enum Mode {
FillDb,
}
fn get_config_file_path() -> PathBuf {
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() {
let mut args = std::env::args().skip(1);
let mode = match args.next().as_ref().map(|v| v.trim()) {
@@ -56,7 +77,9 @@ fn main() {
}
};
let addr = args.next().unwrap_or("127.0.0.1:26314".to_string());
let mut con = TcpStream::connect(addr.parse::<SocketAddr>().unwrap()).unwrap();
let addr = addr.parse::<SocketAddr>().unwrap();
let mut con = TcpStream::connect(addr).unwrap();
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>>>> =
@@ -111,7 +134,15 @@ fn main() {
v.send_event(GuiEvent::Refresh).unwrap();
}
});
gui::main(database, con, sender)
gui::main(
database,
con,
get::Client::new(BufReader::new(
TcpStream::connect(addr).expect("opening get client connection"),
))
.expect("initializing get client connection"),
sender,
)
};
}
Mode::SyncPlayer => {
@@ -134,6 +165,7 @@ fn main() {
let mut line = String::new();
std::io::stdin().read_line(&mut line).unwrap();
if line.trim().to_lowercase() == "yes" {
let mut covers = 0;
for artist in fs::read_dir(&dir)
.expect("reading lib-dir")
.filter_map(|v| v.ok())
@@ -147,6 +179,16 @@ fn main() {
let mut album_id = None;
let mut songs: Vec<_> = songs.filter_map(|v| v.ok()).collect();
songs.sort_unstable_by_key(|v| v.file_name());
let cover = songs.iter().map(|entry| entry.path()).find(|path| {
path.extension().is_some_and(|ext| {
ext.to_str().is_some_and(|ext| {
matches!(
ext.to_lowercase().trim(),
"png" | "jpg" | "jpeg"
)
})
})
});
for song in songs {
match song.path().extension().map(|v| v.to_str()) {
Some(Some(
@@ -229,11 +271,39 @@ fn main() {
drop(db);
if !adding_album {
adding_album = true;
let cover = if let Some(cover) = &cover
{
eprintln!("Adding cover {cover:?}");
Command::AddCover(Cover {
location: DatabaseLocation {
rel_path: PathBuf::from(
artist.file_name(),
)
.join(album.file_name())
.join(
cover
.file_name()
.unwrap(),
),
},
data: Arc::new(Mutex::new((
false, None,
))),
})
.to_bytes(&mut con)
.expect(
"sending AddCover to db failed",
);
covers += 1;
Some(covers - 1)
} else {
None
};
Command::AddAlbum(Album {
id: 0,
name: album_name.clone(),
artist: Some(artist_id),
cover: None,
cover,
songs: vec![],
general: GeneralData::default(),
})