Fixes/refactorings/improvements

Add some "error handling" for authentication fail, album art fetch fail,
or getting status fail (if either of the three fails, then
mpd_info_screen will not try to authenticate/fetch-info again).

Some refactoring like using a struct instead of a tuple.

Improve drawing of text by caching the picked font size that is only
changed when the song changes or when the window dimensions change.
This commit is contained in:
Stephen Seo 2021-09-17 14:22:56 +09:00
parent 6850dced25
commit bf62fde38d

View file

@ -16,6 +16,8 @@ const TITLE_Y_OFFSET: f32 = 20.0f32;
const TIMER_X_OFFSET: f32 = 20.0f32; const TIMER_X_OFFSET: f32 = 20.0f32;
const TIMER_Y_OFFSET: f32 = 20.0f32; const TIMER_Y_OFFSET: f32 = 20.0f32;
const TIME_MAX_DIFF: f64 = 2.0f64; const TIME_MAX_DIFF: f64 = 2.0f64;
const INITIAL_FONT_SIZE: u16 = 96;
const SCREEN_DIFF_MARGIN: f32 = 1.0;
#[derive(StructOpt, Debug)] #[derive(StructOpt, Debug)]
#[structopt(name = "mpd_info_screen")] #[structopt(name = "mpd_info_screen")]
@ -38,6 +40,9 @@ struct Shared {
stream: TcpStream, stream: TcpStream,
password: String, password: String,
dirty: bool, dirty: bool,
can_authenticate: bool,
can_get_album_art: bool,
can_get_status: bool,
} }
impl Shared { impl Shared {
@ -53,10 +58,30 @@ impl Shared {
stream, stream,
password: String::new(), password: String::new(),
dirty: true, dirty: true,
can_authenticate: true,
can_get_album_art: true,
can_get_status: true,
} }
} }
} }
#[derive(Debug, PartialEq, Copy, Clone)]
enum PollState {
None,
Password,
CurrentSong,
Status,
ReadPicture,
}
#[derive(Debug, Clone)]
struct InfoFromShared {
title: String,
length: f64,
pos: f64,
instant_rec: Instant,
}
fn get_connection(host: Ipv4Addr, port: u16) -> Result<TcpStream, String> { fn get_connection(host: Ipv4Addr, port: u16) -> Result<TcpStream, String> {
let stream = TcpStream::connect_timeout( let stream = TcpStream::connect_timeout(
&SocketAddr::new(IpAddr::V4(host), port), &SocketAddr::new(IpAddr::V4(host), port),
@ -232,6 +257,7 @@ fn info_loop(shared_data: Arc<Mutex<Shared>>) -> Result<(), String> {
let mut song_pos_get_time: Instant = Instant::now() - Duration::from_secs(10); let mut song_pos_get_time: Instant = Instant::now() - Duration::from_secs(10);
let mut song_length_get_time: Instant = Instant::now() - Duration::from_secs(10); let mut song_length_get_time: Instant = Instant::now() - Duration::from_secs(10);
let mut current_binary_size: usize = 0; let mut current_binary_size: usize = 0;
let mut poll_state = PollState::None;
'main: loop { 'main: loop {
if !shared_data if !shared_data
.lock() .lock()
@ -258,6 +284,7 @@ fn info_loop(shared_data: Arc<Mutex<Shared>>) -> Result<(), String> {
read_vec = read_vec.split_off(current_binary_size + 1); read_vec = read_vec.split_off(current_binary_size + 1);
count = read_vec.len(); count = read_vec.len();
current_binary_size = 0; current_binary_size = 0;
poll_state = PollState::None;
//println!( //println!(
// "art_data len is {} after fully reading", // "art_data len is {} after fully reading",
// lock.art_data.len() // lock.art_data.len()
@ -279,6 +306,7 @@ fn info_loop(shared_data: Arc<Mutex<Shared>>) -> Result<(), String> {
if line.starts_with("OK MPD ") { if line.starts_with("OK MPD ") {
init = false; init = false;
println!("Got initial \"OK\" from MPD"); println!("Got initial \"OK\" from MPD");
poll_state = PollState::None;
break; break;
} else { } else {
return Err(String::from( return Err(String::from(
@ -288,9 +316,19 @@ fn info_loop(shared_data: Arc<Mutex<Shared>>) -> Result<(), String> {
} else { } else {
//println!("Got response: {}", line); //println!("Got response: {}", line);
if line.starts_with("OK") { if line.starts_with("OK") {
poll_state = PollState::None;
break; break;
} else if line.starts_with("ACK") { } else if line.starts_with("ACK") {
println!("ERROR: {}", line); println!("ERROR: {}", line);
match poll_state {
PollState::Password => lock.can_authenticate = false,
PollState::CurrentSong | PollState::Status => {
lock.can_get_status = false
}
PollState::ReadPicture => lock.can_get_album_art = false,
_ => (),
}
poll_state = PollState::None;
} else if line.starts_with("file: ") { } else if line.starts_with("file: ") {
let song_file = line.split_off(6); let song_file = line.split_off(6);
if song_file != lock.current_song { if song_file != lock.current_song {
@ -347,31 +385,38 @@ fn info_loop(shared_data: Arc<Mutex<Shared>>) -> Result<(), String> {
} }
// write block // write block
{ if poll_state == PollState::None {
let lock_result = shared_data.try_lock(); let lock_result = shared_data.try_lock();
if let Ok(mut lock) = lock_result { if let Ok(mut lock) = lock_result {
if !authenticated && !lock.password.is_empty() { if !authenticated && !lock.password.is_empty() && lock.can_authenticate {
let p = lock.password.clone(); let p = lock.password.clone();
let write_result = lock.stream.write(format!("password {}\n", p).as_bytes()); let write_result = lock.stream.write(format!("password {}\n", p).as_bytes());
if write_result.is_ok() { if write_result.is_ok() {
authenticated = true; authenticated = true;
poll_state = PollState::Password;
} else if let Err(e) = write_result { } else if let Err(e) = write_result {
println!("Got error requesting authentication: {}", e); println!("Got error requesting authentication: {}", e);
} }
} else if song_title_get_time.elapsed() > POLL_DURATION { } else if song_title_get_time.elapsed() > POLL_DURATION && lock.can_get_status {
let write_result = lock.stream.write(b"currentsong\n"); let write_result = lock.stream.write(b"currentsong\n");
if let Err(e) = write_result { if let Err(e) = write_result {
println!("Got error requesting currentsong info: {}", e); println!("Got error requesting currentsong info: {}", e);
} else {
poll_state = PollState::CurrentSong;
} }
} else if song_length_get_time.elapsed() > POLL_DURATION } else if (song_length_get_time.elapsed() > POLL_DURATION
|| song_pos_get_time.elapsed() > POLL_DURATION || song_pos_get_time.elapsed() > POLL_DURATION)
&& lock.can_get_status
{ {
let write_result = lock.stream.write(b"status\n"); let write_result = lock.stream.write(b"status\n");
if let Err(e) = write_result { if let Err(e) = write_result {
println!("Got error requesting status: {}", e); println!("Got error requesting status: {}", e);
} else {
poll_state = PollState::Status;
} }
} else if (lock.art_data.is_empty() || lock.art_data.len() != lock.art_data_size) } else if (lock.art_data.is_empty() || lock.art_data.len() != lock.art_data_size)
&& !lock.current_song.is_empty() && !lock.current_song.is_empty()
&& lock.can_get_album_art
{ {
let title = lock.current_song.clone(); let title = lock.current_song.clone();
let art_data_length = lock.art_data.len(); let art_data_length = lock.art_data.len();
@ -380,11 +425,16 @@ fn info_loop(shared_data: Arc<Mutex<Shared>>) -> Result<(), String> {
); );
if let Err(e) = write_result { if let Err(e) = write_result {
println!("Got error requesting albumart: {}", e); println!("Got error requesting albumart: {}", e);
} else {
poll_state = PollState::ReadPicture;
} }
} }
} else { } else {
println!("Failed to acquire lock for writing to stream"); println!("Failed to acquire lock for writing to stream");
} }
} else {
// TODO DEBUG
//println!("poll_state == {:?}, skipping write...", poll_state);
} }
thread::sleep(Duration::from_millis(50)); thread::sleep(Duration::from_millis(50));
@ -395,7 +445,7 @@ fn info_loop(shared_data: Arc<Mutex<Shared>>) -> Result<(), String> {
fn get_info_from_shared( fn get_info_from_shared(
shared: Arc<Mutex<Shared>>, shared: Arc<Mutex<Shared>>,
force_check: bool, force_check: bool,
) -> Result<(String, f64, f64, Instant), String> { ) -> Result<InfoFromShared, String> {
let mut title: String = String::new(); let mut title: String = String::new();
let mut length: f64 = 0.0; let mut length: f64 = 0.0;
let mut pos: f64 = 0.0; let mut pos: f64 = 0.0;
@ -413,7 +463,12 @@ fn get_info_from_shared(
} }
} }
Ok((title, length, pos, instant_rec)) Ok(InfoFromShared {
title,
length,
pos,
instant_rec,
})
} }
fn window_conf() -> Conf { fn window_conf() -> Conf {
@ -480,6 +535,10 @@ async fn main() -> Result<(), String> {
let mut track_timer: f64 = 0.0; let mut track_timer: f64 = 0.0;
let mut title: String = String::new(); let mut title: String = String::new();
let mut art_texture: Option<Texture2D> = None; let mut art_texture: Option<Texture2D> = None;
let mut filename_font_size: Option<u16> = None;
let mut text_dim: TextDimensions = measure_text("undefined", None, 24, 1.0);
let mut prev_width = screen_width();
let mut prev_height = screen_height();
'macroquad_main: loop { 'macroquad_main: loop {
let dt: f64 = get_frame_time() as f64; let dt: f64 = get_frame_time() as f64;
@ -487,6 +546,12 @@ async fn main() -> Result<(), String> {
if is_key_pressed(KeyCode::Escape) || is_key_pressed(KeyCode::Q) { if is_key_pressed(KeyCode::Escape) || is_key_pressed(KeyCode::Q) {
break 'macroquad_main; break 'macroquad_main;
} else if (prev_width - screen_width()).abs() > SCREEN_DIFF_MARGIN
|| (prev_height - screen_height()).abs() > SCREEN_DIFF_MARGIN
{
prev_width = screen_width();
prev_height = screen_height();
filename_font_size = None;
} }
timer -= dt; timer -= dt;
@ -494,15 +559,16 @@ async fn main() -> Result<(), String> {
if timer < 0.0 || track_timer < 0.0 { if timer < 0.0 || track_timer < 0.0 {
timer = wait_time; timer = wait_time;
let info_result = get_info_from_shared(shared_data.clone(), true); let info_result = get_info_from_shared(shared_data.clone(), true);
if let Ok((track_title, duration, pos, instant_rec)) = info_result { if let Ok(info) = info_result {
if track_title != title { if info.title != title {
title = track_title; title = info.title;
art_texture = None; art_texture = None;
filename_font_size = None;
} }
let duration_since = instant_rec.elapsed(); let duration_since = info.instant_rec.elapsed();
let recorded_time = duration - pos - duration_since.as_secs_f64(); let recorded_time = info.length - info.pos - duration_since.as_secs_f64();
if (recorded_time - track_timer).abs() > TIME_MAX_DIFF { if (recorded_time - track_timer).abs() > TIME_MAX_DIFF {
track_timer = duration - pos; track_timer = info.length - info.pos - duration_since.as_secs_f64();
} }
} }
@ -532,15 +598,15 @@ async fn main() -> Result<(), String> {
} }
if let Some(texture) = art_texture { if let Some(texture) = art_texture {
if texture.width() > screen_width() || texture.height() > screen_height() { if texture.width() > prev_width || texture.height() > prev_height {
let ratio: f32 = texture.width() / texture.height(); let ratio: f32 = texture.width() / texture.height();
// try filling to height // try filling to height
let mut height = screen_height(); let mut height = prev_height;
let mut width = screen_height() * ratio; let mut width = prev_height * ratio;
if width > screen_width() { if width > prev_width {
// try filling to width instead // try filling to width instead
width = screen_width(); width = prev_width;
height = screen_width() / ratio; height = prev_width / ratio;
} }
let draw_params: DrawTextureParams = DrawTextureParams { let draw_params: DrawTextureParams = DrawTextureParams {
@ -549,40 +615,48 @@ async fn main() -> Result<(), String> {
}; };
draw_texture_ex( draw_texture_ex(
texture, texture,
(screen_width() - width) / 2.0f32, (prev_width - width) / 2.0f32,
(screen_height() - height) / 2.0f32, (prev_height - height) / 2.0f32,
WHITE, WHITE,
draw_params, draw_params,
); );
} else { } else {
draw_texture( draw_texture(
texture, texture,
(screen_width() - texture.width()) / 2.0f32, (prev_width - texture.width()) / 2.0f32,
(screen_height() - texture.height()) / 2.0f32, (prev_height - texture.height()) / 2.0f32,
WHITE, WHITE,
); );
} }
} }
if !title.is_empty() { if !title.is_empty() {
let mut text_size = 64; if filename_font_size.is_none() {
let mut text_dim: TextDimensions; filename_font_size = Some(INITIAL_FONT_SIZE);
loop { loop {
text_dim = measure_text(&title, None, text_size, 1.0f32); text_dim =
if text_dim.width + TITLE_X_OFFSET > screen_width() { measure_text(&title, None, *filename_font_size.as_ref().unwrap(), 1.0f32);
text_size -= 4; if text_dim.width + TITLE_X_OFFSET > prev_width {
} else { filename_font_size = filename_font_size.map(|s| s - 4);
break; } else {
} break;
}
if text_size <= 4 { if *filename_font_size.as_ref().unwrap() <= 4 {
text_size = 4; filename_font_size = Some(4);
break; text_dim = measure_text(
&title,
None,
*filename_font_size.as_ref().unwrap(),
1.0f32,
);
break;
}
} }
} }
draw_rectangle( draw_rectangle(
TITLE_X_OFFSET, TITLE_X_OFFSET,
screen_height() - TITLE_Y_OFFSET - text_dim.height * 2.0, prev_height - TITLE_Y_OFFSET - text_dim.height * 2.0,
text_dim.width, text_dim.width,
text_dim.height, text_dim.height,
Color::new(0.0, 0.0, 0.0, 0.4), Color::new(0.0, 0.0, 0.0, 0.4),
@ -590,8 +664,8 @@ async fn main() -> Result<(), String> {
draw_text( draw_text(
&title, &title,
TITLE_X_OFFSET, TITLE_X_OFFSET,
screen_height() - TITLE_Y_OFFSET - text_dim.height, prev_height - TITLE_Y_OFFSET - text_dim.height,
text_size as f32, *filename_font_size.as_ref().unwrap() as f32,
WHITE, WHITE,
); );
@ -599,7 +673,7 @@ async fn main() -> Result<(), String> {
let timer_dim = measure_text(&timer_string, None, 64, 1.0f32); let timer_dim = measure_text(&timer_string, None, 64, 1.0f32);
draw_rectangle( draw_rectangle(
TIMER_X_OFFSET, TIMER_X_OFFSET,
screen_height() prev_height
- TITLE_Y_OFFSET - TITLE_Y_OFFSET
- text_dim.height - text_dim.height
- TIMER_Y_OFFSET - TIMER_Y_OFFSET
@ -611,11 +685,7 @@ async fn main() -> Result<(), String> {
draw_text( draw_text(
&timer_string, &timer_string,
TIMER_X_OFFSET, TIMER_X_OFFSET,
screen_height() prev_height - TITLE_Y_OFFSET - text_dim.height - TIMER_Y_OFFSET - timer_dim.height,
- TITLE_Y_OFFSET
- text_dim.height
- TIMER_Y_OFFSET
- timer_dim.height,
64.0f32, 64.0f32,
WHITE, WHITE,
); );