use std::io::{Read, Write}; use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream}; use std::str::FromStr; use std::sync::{Arc, Mutex}; use std::thread; use std::time::{Duration, Instant}; use structopt::StructOpt; #[derive(StructOpt, Debug)] #[structopt(name = "mpd_info_screen")] struct Opt { host: Ipv4Addr, #[structopt(default_value = "6600")] port: u16, #[structopt(short = "p")] password: Option, } struct Shared { art_data: Vec, current_song: String, current_song_length: f64, current_song_position: f64, thread_running: bool, stream: TcpStream, password: String, dirty: bool, } impl Shared { fn new(stream: TcpStream) -> Self { Self { art_data: Vec::new(), current_song: String::new(), current_song_length: 0.0, current_song_position: 0.0, thread_running: true, stream, password: String::new(), dirty: true, } } } fn get_connection(host: Ipv4Addr, port: u16) -> Result { let stream = TcpStream::connect_timeout( &SocketAddr::new(IpAddr::V4(host), port), Duration::from_secs(5), ) .map_err(|_| String::from("Failed to get connection"))?; Ok(stream) } fn check_next_chars( buf: &[u8], idx: usize, saved: &mut Vec, ) -> Result<(char, u8), (String, u8)> { if idx >= buf.len() { return Err((String::from("idx out of bounds"), 0u8)); } if buf[idx] & 0b10000000 == 0 { Ok(( char::from_u32(buf[idx] as u32) .ok_or_else(|| (String::from("Not one-byte UTF-8"), 0u8))?, 1u8, )) } else if buf[idx] & 0b11100000 == 0b11000000 { if idx + 1 >= buf.len() { saved.push(buf[idx]); return Err(( String::from("Is two byte UTF-8, but not enough bytes provided"), 1u8, )); } Ok(( char::from_u32((buf[idx] as u32) | ((buf[idx + 1] as u32) << 8)) .ok_or_else(|| (String::from("Not two-byte UTF-8"), 0u8))?, 2u8, )) } else if buf[idx] & 0b11110000 == 0b11100000 { if idx + 2 >= buf.len() { for tidx in idx..buf.len() { saved.push(buf[tidx]); } return Err(( String::from("Is three byte UTF-8, but not enough bytes provided"), (idx + 3 - buf.len()) as u8, )); } Ok(( char::from_u32( (buf[idx] as u32) | ((buf[idx + 1] as u32) << 8) | ((buf[idx + 2] as u32) << 16), ) .ok_or_else(|| (String::from("Not three-byte UTF-8"), 0u8))?, 3u8, )) } else if buf[idx] & 0b11111000 == 0b11110000 { if idx + 2 >= buf.len() { for tidx in idx..buf.len() { saved.push(buf[tidx]); } return Err(( String::from("Is four byte UTF-8, but not enough bytes provided"), (idx + 4 - buf.len()) as u8, )); } Ok(( char::from_u32( (buf[idx] as u32) | ((buf[idx + 1] as u32) << 8) | ((buf[idx + 2] as u32) << 16) | ((buf[idx + 3] as u32) << 24), ) .ok_or_else(|| (String::from("Not four-byte UTF-8"), 0u8))?, 4u8, )) } else { Err((String::from("Invalid UTF-8 char"), 0u8)) } } fn read_line( buf: &mut Vec, count: usize, saved: &mut Vec, init: bool, ) -> Result { let mut result = String::new(); if !saved.is_empty() { // TODO println!("TODO handle \"saved\" vec"); } saved.clear(); let mut prev_two: Vec = Vec::with_capacity(3); let mut skip_count = 0; for idx in 0..count { if skip_count > 0 { skip_count -= 1; continue; } let next_char_result = check_next_chars(buf, idx, saved); if let Ok((c, s)) = next_char_result { if !init { prev_two.push(c); if prev_two.len() > 2 { prev_two.remove(0); } if ['O', 'K'] == prev_two.as_slice() { *buf = buf.split_off(2); result = String::from("OK"); return Ok(result); } } if c == '\n' { *buf = buf.split_off(idx + s as usize); return Ok(result); } result.push(c); skip_count = s - 1; } else if let Err((msg, count)) = next_char_result { for i in 0..count { saved.push(buf[idx + i as usize]); } *buf = buf.split_off(idx); return Err((msg, result)); } else { unreachable!(); } } *saved = buf.to_vec(); *buf = Vec::new(); Err((String::from("Newline not reached"), result)) } fn info_loop(shared_data: Arc>) -> Result<(), String> { let mut buf: [u8; 4192] = [0; 4192]; let mut init: bool = true; let mut saved: Vec = Vec::new(); let mut saved_str: String = String::new(); let mut authenticated: bool = false; let mut song_title_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); loop { if !shared_data .lock() .map_err(|_| String::from("Failed to get shared_data.thread_running"))? .thread_running { break; } // read block { let lock_result = shared_data.try_lock(); if let Ok(mut lock) = lock_result { let read_result = lock.stream.read(&mut buf); if let Ok(count) = read_result { let mut read_vec: Vec = Vec::from(buf); read_vec.resize(count, 0); loop { let count = read_vec.len(); let read_line_result = read_line(&mut read_vec, count, &mut saved, init); if let Ok(mut line) = read_line_result { line = saved_str + &line; saved_str = String::new(); if init { if line.starts_with("OK MPD ") { init = false; println!("Got initial \"OK\" from MPD"); break; } else { return Err(String::from( "Did not get expected init message from MPD", )); } } else { // TODO handling of other messages println!("Got response: {}", line); if line.starts_with("OK") { break; } else if line.starts_with("file: ") { lock.current_song = line.split_off(6); lock.dirty = true; song_title_get_time = Instant::now(); } else if line.starts_with("elapsed: ") { let parse_pos_result = f64::from_str(&line.split_off(9)); if let Ok(value) = parse_pos_result { lock.current_song_position = value; lock.dirty = true; song_pos_get_time = Instant::now(); } else { println!("Got error trying to get current_song_position"); } } else if line.starts_with("duration: ") { let parse_pos_result = f64::from_str(&line.split_off(10)); if let Ok(value) = parse_pos_result { lock.current_song_length = value; lock.dirty = true; song_length_get_time = Instant::now(); } } } } else if let Err((msg, read_line_in_progress)) = read_line_result { println!("Error during \"read_line\": {}", msg); saved_str = read_line_in_progress; break; } else { unreachable!(); } } } } else { println!("Failed to acquire lock for reading to stream"); } } // TODO send messages to get info // write block { let lock_result = shared_data.try_lock(); if let Ok(mut lock) = lock_result { if !authenticated && !lock.password.is_empty() { let p = lock.password.clone(); let write_result = lock.stream.write(format!("password {}\n", p).as_bytes()); if write_result.is_ok() { authenticated = true; } else if let Err(e) = write_result { println!("Got error requesting authentication: {}", e); } } else if song_title_get_time.elapsed() > Duration::from_secs(5) { let write_result = lock.stream.write(b"currentsong\n"); if let Err(e) = write_result { println!("Got error requesting currentsong info: {}", e); } } else if song_length_get_time.elapsed() > Duration::from_secs(5) || song_pos_get_time.elapsed() > Duration::from_secs(5) { let write_result = lock.stream.write(b"status\n"); if let Err(e) = write_result { println!("Got error requesting status: {}", e); } } } else { println!("Failed to acquire lock for writing to stream"); } } thread::sleep(Duration::from_millis(50)); } Ok(()) } fn get_info_from_shared(shared: Arc>, force_check: bool) -> Result<(), String> { if let Ok(mut lock) = shared.lock() { if lock.dirty || force_check { println!("Current song: {}", lock.current_song); println!("Current song length: {}", lock.current_song_length); println!("Current song position: {}", lock.current_song_position); lock.dirty = false; } } Ok(()) } fn main() -> Result<(), String> { let opt = Opt::from_args(); println!("Got host addr == {}, port == {}", opt.host, opt.port); let connection = get_connection(opt.host, opt.port)?; connection .set_read_timeout(Some(Duration::from_millis(50))) .expect("Should be able to set timeout for TcpStream reads"); connection .set_write_timeout(Some(Duration::from_secs(1))) .expect("Should be able to set timeout for TcpStream writes"); let shared_data = Arc::new(Mutex::new(Shared::new(connection))); if let Some(p) = opt.password { shared_data .lock() .expect("Should be able to get mutex lock") .password = p; } let thread_shared_data = shared_data.clone(); let child = thread::spawn(move || { info_loop(thread_shared_data).expect("Failure during info_loop"); }); thread::sleep(Duration::from_secs(2)); get_info_from_shared(shared_data.clone(), false) .expect("Should be able to get info from shared"); thread::sleep(Duration::from_secs(2)); println!("Stopping thread..."); shared_data .lock() .map_err(|_| String::from("Failed to get shared_data.thread_running in main"))? .thread_running = false; println!("Waiting on thread..."); thread::sleep(Duration::from_secs(2)); println!("Joining on thread..."); child.join().expect("Should be able to join on thread"); get_info_from_shared(shared_data.clone(), true) .expect("Should be able to get info from shared"); Ok(()) }