rmptuiad32349
refactor: consolidate MPD TCP connection
Keep the TCP stream and parsed player state in one Connection, and route app commands directly through it. Preserve MPD directory URIs in browser entries and use recursive directory adds for queue actions.
+151 −147
5 files changed
- M src/app.rs +46 −51
- M src/browser.rs +53 −3
- M src/connection.rs +42 −76
- M src/event_handler/handler.rs +8 −15
- M src/main.rs +2 −2
| @@ -60,9 +60,9 @@ impl App { | |||
| 60 | 60 | pub async fn new(address: &str, password: Option<&str>, config: Config) -> AppResult<Self> { | |
| 61 | 61 | let mut conn = Connection::connect(address, password).await?; | |
| 62 | 62 | let mut queue_list = ContentList::new(); | |
| 63 | − | queue_list.list = conn.client.queue().await?; | |
| 63 | + | queue_list.list = conn.queue().await?; | |
| 64 | 64 | let mut pl_list = ContentList::new(); | |
| 65 | − | pl_list.list = conn.client.playlists().await?; | |
| 65 | + | pl_list.list = conn.playlists().await?; | |
| 66 | 66 | pl_list.list.sort(); | |
| 67 | 67 | let append_list = Self::get_append_list(&mut conn).await?; | |
| 68 | 68 | let mut browser = FileBrowser::new(); | |
| @@ -133,7 +133,7 @@ impl App { | |||
| 133 | 133 | } | |
| 134 | 134 | ||
| 135 | 135 | pub async fn update_queue(&mut self) -> AppResult<()> { | |
| 136 | − | self.queue_list.list = self.conn.client.queue().await?; | |
| 136 | + | self.queue_list.list = self.conn.queue().await?; | |
| 137 | 137 | if self.queue_list.list.is_empty() { | |
| 138 | 138 | self.queue_list.index = 0; | |
| 139 | 139 | } else { | |
| @@ -143,7 +143,7 @@ impl App { | |||
| 143 | 143 | } | |
| 144 | 144 | ||
| 145 | 145 | pub async fn refresh_playlists(&mut self) -> AppResult<()> { | |
| 146 | − | self.pl_list.list = self.conn.client.playlists().await?; | |
| 146 | + | self.pl_list.list = self.conn.playlists().await?; | |
| 147 | 147 | self.pl_list.list.sort(); | |
| 148 | 148 | self.pl_list.index = self | |
| 149 | 149 | .pl_list | |
| @@ -155,7 +155,7 @@ impl App { | |||
| 155 | 155 | ||
| 156 | 156 | pub async fn refresh_playlist_view(&mut self) -> AppResult<()> { | |
| 157 | 157 | self.playlist_contents = match self.pl_list.list.get(self.pl_list.index) { | |
| 158 | − | Some(name) => self.conn.client.playlist(name).await?, | |
| 158 | + | Some(name) => self.conn.playlist(name).await?, | |
| 159 | 159 | None => Vec::new(), | |
| 160 | 160 | }; | |
| 161 | 161 | self.playlist_song_index = self | |
| @@ -172,7 +172,6 @@ impl App { | |||
| 172 | 172 | return Ok(()); | |
| 173 | 173 | } | |
| 174 | 174 | self.conn | |
| 175 | − | .client | |
| 176 | 175 | .delete_playlist_song(playlist, self.playlist_song_index) | |
| 177 | 176 | .await?; | |
| 178 | 177 | self.refresh_playlist_view().await | |
| @@ -191,7 +190,6 @@ impl App { | |||
| 191 | 190 | return Ok(()); | |
| 192 | 191 | }; | |
| 193 | 192 | self.conn | |
| 194 | − | .client | |
| 195 | 193 | .move_playlist_song(playlist, self.playlist_song_index, target) | |
| 196 | 194 | .await?; | |
| 197 | 195 | self.playlist_song_index = target; | |
| @@ -202,7 +200,7 @@ impl App { | |||
| 202 | 200 | let Some(playlist) = self.pl_list.list.get(self.pl_list.index) else { | |
| 203 | 201 | return Ok(()); | |
| 204 | 202 | }; | |
| 205 | − | self.conn.client.delete_playlist(playlist).await?; | |
| 203 | + | self.conn.delete_playlist(playlist).await?; | |
| 206 | 204 | self.refresh_playlists().await?; | |
| 207 | 205 | Ok(()) | |
| 208 | 206 | } | |
| @@ -214,10 +212,9 @@ impl App { | |||
| 214 | 212 | if self.playlist_song_index >= self.playlist_contents.len() { | |
| 215 | 213 | return Ok(()); | |
| 216 | 214 | } | |
| 217 | − | let queue_position = self.conn.client.queue().await?.len(); | |
| 218 | − | self.conn.client.append_playlist(playlist).await?; | |
| 215 | + | let queue_position = self.conn.queue().await?.len(); | |
| 216 | + | self.conn.append_playlist(playlist).await?; | |
| 219 | 217 | self.conn | |
| 220 | − | .client | |
| 221 | 218 | .play(Some((queue_position + self.playlist_song_index) as u32)) | |
| 222 | 219 | .await?; | |
| 223 | 220 | self.update_queue().await?; | |
| @@ -228,7 +225,7 @@ impl App { | |||
| 228 | 225 | let mut list = ContentList::new(); | |
| 229 | 226 | list.list.push("Current Playlist".to_string()); | |
| 230 | 227 | list.list.push("New Playlist".to_string()); | |
| 231 | − | list.list.extend(conn.client.playlists().await?); | |
| 228 | + | list.list.extend(conn.playlists().await?); | |
| 232 | 229 | Ok(list) | |
| 233 | 230 | } | |
| 234 | 231 | ||
| @@ -240,12 +237,7 @@ impl App { | |||
| 240 | 237 | }; | |
| 241 | 238 | match entry { | |
| 242 | 239 | BrowserEntry::Directory(name) => { | |
| 243 | − | let path = join_library_path(&self.browser.path, &name); | |
| 244 | − | for (entry_type, file) in self.conn.client.list_files(&path).await? { | |
| 245 | − | if entry_type == "file" && is_audio_file(&file) { | |
| 246 | − | self.conn.client.add(&file).await?; | |
| 247 | − | } | |
| 248 | − | } | |
| 240 | + | self.conn.add(&name).await?; | |
| 249 | 241 | } | |
| 250 | 242 | BrowserEntry::Song(song) => { | |
| 251 | 243 | let positions: Vec<u32> = self | |
| @@ -258,10 +250,10 @@ impl App { | |||
| 258 | 250 | }) | |
| 259 | 251 | .collect(); | |
| 260 | 252 | if positions.is_empty() { | |
| 261 | − | self.conn.client.add(&song.file).await?; | |
| 253 | + | self.conn.add(&song.file).await?; | |
| 262 | 254 | } else { | |
| 263 | 255 | for position in positions.into_iter().rev() { | |
| 264 | − | self.conn.client.delete(position).await?; | |
| 256 | + | self.conn.delete(position).await?; | |
| 265 | 257 | } | |
| 266 | 258 | } | |
| 267 | 259 | } | |
| @@ -272,10 +264,7 @@ impl App { | |||
| 272 | 264 | } | |
| 273 | 265 | SelectedTab::Queue => { | |
| 274 | 266 | if !self.queue_list.list.is_empty() { | |
| 275 | − | self.conn | |
| 276 | − | .client | |
| 277 | − | .delete(self.queue_list.index as u32) | |
| 278 | − | .await?; | |
| 267 | + | self.conn.delete(self.queue_list.index as u32).await?; | |
| 279 | 268 | } | |
| 280 | 269 | } | |
| 281 | 270 | SelectedTab::Playlists => {} | |
| @@ -300,7 +289,7 @@ impl App { | |||
| 300 | 289 | match entry { | |
| 301 | 290 | BrowserEntry::Directory(name) => { | |
| 302 | 291 | self.browser.prev_selected = self.browser.selected; | |
| 303 | − | self.browser.path = join_library_path(&self.browser.path, &name); | |
| 292 | + | self.browser.path = name; | |
| 304 | 293 | self.browser.selected = 0; | |
| 305 | 294 | self.browser.update_directory(&mut self.conn).await?; | |
| 306 | 295 | } | |
| @@ -311,7 +300,7 @@ impl App { | |||
| 311 | 300 | .iter() | |
| 312 | 301 | .position(|queued| queued.file == song.file) | |
| 313 | 302 | { | |
| 314 | − | self.conn.client.play(Some(index as u32)).await?; | |
| 303 | + | self.conn.play(Some(index as u32)).await?; | |
| 315 | 304 | } else { | |
| 316 | 305 | self.conn.push(&song).await?; | |
| 317 | 306 | self.update_queue().await?; | |
| @@ -340,26 +329,23 @@ impl App { | |||
| 340 | 329 | }; | |
| 341 | 330 | match entry { | |
| 342 | 331 | BrowserEntry::Song(song) => vec![song], | |
| 343 | − | BrowserEntry::Directory(name) => { | |
| 344 | − | let path = join_library_path(&self.browser.path, &name); | |
| 345 | − | self.conn | |
| 346 | − | .client | |
| 347 | − | .list_files(&path) | |
| 348 | − | .await? | |
| 349 | − | .into_iter() | |
| 350 | − | .filter_map(|(kind, file)| { | |
| 351 | − | (kind == "file" && is_audio_file(&file)) | |
| 352 | − | .then(|| Connection::song_from_file(&file)) | |
| 353 | − | }) | |
| 354 | − | .collect() | |
| 355 | − | } | |
| 332 | + | BrowserEntry::Directory(name) => self | |
| 333 | + | .conn | |
| 334 | + | .list_files(&name) | |
| 335 | + | .await? | |
| 336 | + | .into_iter() | |
| 337 | + | .filter_map(|(kind, file)| { | |
| 338 | + | (kind == "file" && is_audio_file(&file)) | |
| 339 | + | .then(|| Connection::song_from_file(&file)) | |
| 340 | + | }) | |
| 341 | + | .collect(), | |
| 356 | 342 | } | |
| 357 | 343 | } | |
| 358 | 344 | SelectedTab::Playlists => { | |
| 359 | 345 | let Some(name) = self.pl_list.list.get(self.pl_list.index) else { | |
| 360 | 346 | return Ok(()); | |
| 361 | 347 | }; | |
| 362 | − | self.conn.client.playlist(name).await? | |
| 348 | + | self.conn.playlist(name).await? | |
| 363 | 349 | } | |
| 364 | 350 | }; | |
| 365 | 351 | ||
| @@ -370,8 +356,26 @@ impl App { | |||
| 370 | 356 | self.update_queue().await?; | |
| 371 | 357 | } | |
| 372 | 358 | } else { | |
| 373 | − | for song in songs { | |
| 374 | − | self.conn.client.add(&song.file).await?; | |
| 359 | + | let selected_directory = if self.selected_tab == SelectedTab::DirectoryBrowser { | |
| 360 | + | self.browser | |
| 361 | + | .entries | |
| 362 | + | .get(self.browser.selected) | |
| 363 | + | .and_then(|entry| { | |
| 364 | + | if let BrowserEntry::Directory(path) = entry { | |
| 365 | + | Some(path.as_str()) | |
| 366 | + | } else { | |
| 367 | + | None | |
| 368 | + | } | |
| 369 | + | }) | |
| 370 | + | } else { | |
| 371 | + | None | |
| 372 | + | }; | |
| 373 | + | if let Some(path) = selected_directory { | |
| 374 | + | self.conn.add(path).await?; | |
| 375 | + | } else { | |
| 376 | + | for song in songs { | |
| 377 | + | self.conn.add(&song.file).await?; | |
| 378 | + | } | |
| 375 | 379 | } | |
| 376 | 380 | self.update_queue().await?; | |
| 377 | 381 | } | |
| @@ -495,12 +499,3 @@ impl App { | |||
| 495 | 499 | } | |
| 496 | 500 | } | |
| 497 | 501 | } | |
| 498 | − | ||
| 499 | − | pub fn join_library_path(parent: &str, child: &str) -> String { | |
| 500 | − | let parent = parent.trim_matches('/').trim_start_matches("./"); | |
| 501 | − | if parent.is_empty() || parent == "." { | |
| 502 | − | child.trim_start_matches("./").to_string() | |
| 503 | − | } else { | |
| 504 | − | format!("{parent}/{}", child.trim_start_matches("./")) | |
| 505 | − | } | |
| 506 | − | } | |
| @@ -23,7 +23,10 @@ pub enum BrowserEntry { | |||
| 23 | 23 | impl BrowserEntry { | |
| 24 | 24 | pub fn name(&self) -> &str { | |
| 25 | 25 | match self { | |
| 26 | − | Self::Directory(name) => name, | |
| 26 | + | Self::Directory(path) => Path::new(path) | |
| 27 | + | .file_name() | |
| 28 | + | .and_then(|name| name.to_str()) | |
| 29 | + | .unwrap_or(path), | |
| 27 | 30 | Self::Song(song) => Path::new(&song.file) | |
| 28 | 31 | .file_name() | |
| 29 | 32 | .and_then(|name| name.to_str()) | |
| @@ -42,7 +45,7 @@ impl FileBrowser { | |||
| 42 | 45 | ||
| 43 | 46 | pub async fn update_directory(&mut self, conn: &mut Connection) -> AppResult<()> { | |
| 44 | 47 | let query_path = self.path.trim_start_matches("./").trim_matches('/'); | |
| 45 | − | let (directories, songs) = conn.client.ls_info(query_path).await?; | |
| 48 | + | let (directories, songs) = conn.ls_info(query_path).await?; | |
| 46 | 49 | let mut entries = Vec::new(); | |
| 47 | 50 | for directory in directories { | |
| 48 | 51 | if let Some(name) = Path::new(&directory) | |
| @@ -50,7 +53,7 @@ impl FileBrowser { | |||
| 50 | 53 | .and_then(|name| name.to_str()) | |
| 51 | 54 | { | |
| 52 | 55 | if !name.starts_with('.') { | |
| 53 | − | entries.push(BrowserEntry::Directory(name.to_string())); | |
| 56 | + | entries.push(BrowserEntry::Directory(directory)); | |
| 54 | 57 | } | |
| 55 | 58 | } | |
| 56 | 59 | } | |
| @@ -112,3 +115,50 @@ impl FileBrowser { | |||
| 112 | 115 | Ok(()) | |
| 113 | 116 | } | |
| 114 | 117 | } | |
| 118 | + | ||
| 119 | + | #[cfg(test)] | |
| 120 | + | mod tests { | |
| 121 | + | use super::*; | |
| 122 | + | use tokio::{ | |
| 123 | + | io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, | |
| 124 | + | net::TcpListener, | |
| 125 | + | }; | |
| 126 | + | ||
| 127 | + | #[tokio::test] | |
| 128 | + | async fn preserves_mpd_directory_uris_and_displays_their_basename() { | |
| 129 | + | let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); | |
| 130 | + | let address = listener.local_addr().unwrap(); | |
| 131 | + | let server = tokio::spawn(async move { | |
| 132 | + | let (stream, _) = listener.accept().await.unwrap(); | |
| 133 | + | let mut stream = BufReader::new(stream); | |
| 134 | + | stream | |
| 135 | + | .get_mut() | |
| 136 | + | .write_all(b"OK MPD 0.23.0\n") | |
| 137 | + | .await | |
| 138 | + | .unwrap(); | |
| 139 | + | ||
| 140 | + | for response in ["OK\n", "OK\n", "OK\n", "directory: library/Artist\nOK\n"] { | |
| 141 | + | let mut command = String::new(); | |
| 142 | + | stream.read_line(&mut command).await.unwrap(); | |
| 143 | + | stream | |
| 144 | + | .get_mut() | |
| 145 | + | .write_all(response.as_bytes()) | |
| 146 | + | .await | |
| 147 | + | .unwrap(); | |
| 148 | + | } | |
| 149 | + | }); | |
| 150 | + | ||
| 151 | + | let mut conn = Connection::connect(&address.to_string(), None) | |
| 152 | + | .await | |
| 153 | + | .unwrap(); | |
| 154 | + | let mut browser = FileBrowser::new(); | |
| 155 | + | browser.update_directory(&mut conn).await.unwrap(); | |
| 156 | + | ||
| 157 | + | assert_eq!( | |
| 158 | + | browser.entries, | |
| 159 | + | vec![BrowserEntry::Directory("library/Artist".to_string())] | |
| 160 | + | ); | |
| 161 | + | assert_eq!(browser.entries[0].name(), "Artist"); | |
| 162 | + | server.await.unwrap(); | |
| 163 | + | } | |
| 164 | + | } | |
| @@ -78,15 +78,25 @@ impl fmt::Display for MpdError { | |||
| 78 | 78 | impl Error for MpdError {} | |
| 79 | 79 | ||
| 80 | 80 | #[derive(Debug)] | |
| 81 | − | pub struct MpdClient { | |
| 81 | + | pub struct Connection { | |
| 82 | 82 | stream: BufReader<TcpStream>, | |
| 83 | + | pub current_song: Song, | |
| 84 | + | pub stats: Stats, | |
| 85 | + | pub status: Status, | |
| 86 | + | pub volume_status: VolumeStatus, | |
| 87 | + | last_status_update: Instant, | |
| 83 | 88 | } | |
| 84 | 89 | ||
| 85 | − | impl MpdClient { | |
| 86 | − | pub async fn connect(address: &str, password: Option<&str>) -> AppResult<Self> { | |
| 90 | + | impl Connection { | |
| 91 | + | async fn open(address: &str, password: Option<&str>) -> AppResult<Self> { | |
| 87 | 92 | let stream = TcpStream::connect(address).await?; | |
| 88 | 93 | let mut client = Self { | |
| 89 | 94 | stream: BufReader::new(stream), | |
| 95 | + | current_song: Song::default(), | |
| 96 | + | stats: Stats::default(), | |
| 97 | + | status: Status::default(), | |
| 98 | + | volume_status: VolumeStatus::Unmuted, | |
| 99 | + | last_status_update: Instant::now(), | |
| 90 | 100 | }; | |
| 91 | 101 | let mut greeting = String::new(); | |
| 92 | 102 | client.stream.read_line(&mut greeting).await?; | |
| @@ -132,7 +142,7 @@ impl MpdClient { | |||
| 132 | 142 | } | |
| 133 | 143 | } | |
| 134 | 144 | ||
| 135 | − | pub async fn status(&mut self) -> AppResult<Status> { | |
| 145 | + | async fn read_status(&mut self) -> AppResult<Status> { | |
| 136 | 146 | let fields = fields(self.command("status", &[]).await?); | |
| 137 | 147 | let time = fields.get("time").and_then(|s| { | |
| 138 | 148 | let (elapsed, total) = s.split_once(':')?; | |
| @@ -171,7 +181,7 @@ impl MpdClient { | |||
| 171 | 181 | }) | |
| 172 | 182 | } | |
| 173 | 183 | ||
| 174 | − | pub async fn stats(&mut self) -> AppResult<Stats> { | |
| 184 | + | async fn read_stats(&mut self) -> AppResult<Stats> { | |
| 175 | 185 | let fields = fields(self.command("stats", &[]).await?); | |
| 176 | 186 | Ok(Stats { | |
| 177 | 187 | songs: fields | |
| @@ -181,7 +191,7 @@ impl MpdClient { | |||
| 181 | 191 | }) | |
| 182 | 192 | } | |
| 183 | 193 | ||
| 184 | − | pub async fn current_song(&mut self) -> AppResult<Song> { | |
| 194 | + | async fn read_current_song(&mut self) -> AppResult<Song> { | |
| 185 | 195 | Ok(parse_songs(self.command("currentsong", &[]).await?) | |
| 186 | 196 | .into_iter() | |
| 187 | 197 | .next() | |
| @@ -193,9 +203,9 @@ impl MpdClient { | |||
| 193 | 203 | } | |
| 194 | 204 | ||
| 195 | 205 | pub async fn snapshot(&mut self) -> AppResult<MpdSnapshot> { | |
| 196 | − | let status = self.status().await?; | |
| 197 | − | let stats = self.stats().await?; | |
| 198 | − | let current_song = self.current_song().await?; | |
| 206 | + | let status = self.read_status().await?; | |
| 207 | + | let stats = self.read_stats().await?; | |
| 208 | + | let current_song = self.read_current_song().await?; | |
| 199 | 209 | let queue = self.queue().await?; | |
| 200 | 210 | Ok(MpdSnapshot { | |
| 201 | 211 | status, | |
| @@ -287,17 +297,6 @@ impl MpdClient { | |||
| 287 | 297 | Ok(()) | |
| 288 | 298 | } | |
| 289 | 299 | ||
| 290 | − | pub async fn pause(&mut self, paused: bool) -> AppResult<()> { | |
| 291 | − | self.command("pause", &[if paused { "1" } else { "0" }]) | |
| 292 | − | .await?; | |
| 293 | − | Ok(()) | |
| 294 | − | } | |
| 295 | − | ||
| 296 | − | pub async fn toggle_pause(&mut self) -> AppResult<()> { | |
| 297 | − | self.command("pause", &[]).await?; | |
| 298 | − | Ok(()) | |
| 299 | − | } | |
| 300 | − | ||
| 301 | 300 | pub async fn set_repeat(&mut self, enabled: bool) -> AppResult<()> { | |
| 302 | 301 | self.command("repeat", &[if enabled { "1" } else { "0" }]) | |
| 303 | 302 | .await?; | |
| @@ -351,21 +350,11 @@ impl MpdClient { | |||
| 351 | 350 | Ok(()) | |
| 352 | 351 | } | |
| 353 | 352 | ||
| 354 | − | pub async fn load_playlist(&mut self, name: &str) -> AppResult<()> { | |
| 355 | − | self.append_playlist(name).await?; | |
| 356 | − | self.play(None).await | |
| 357 | − | } | |
| 358 | − | ||
| 359 | 353 | pub async fn append_playlist(&mut self, name: &str) -> AppResult<()> { | |
| 360 | 354 | self.command("load", &[name]).await?; | |
| 361 | 355 | Ok(()) | |
| 362 | 356 | } | |
| 363 | 357 | ||
| 364 | − | pub async fn add_to_playlist(&mut self, playlist: &str, file: &str) -> AppResult<()> { | |
| 365 | − | self.command("playlistadd", &[playlist, file]).await?; | |
| 366 | − | Ok(()) | |
| 367 | − | } | |
| 368 | − | ||
| 369 | 358 | pub async fn delete_playlist_song(&mut self, playlist: &str, position: usize) -> AppResult<()> { | |
| 370 | 359 | self.command("playlistdelete", &[playlist, &position.to_string()]) | |
| 371 | 360 | .await?; | |
| @@ -418,30 +407,11 @@ impl MpdClient { | |||
| 418 | 407 | } | |
| 419 | 408 | } | |
| 420 | 409 | ||
| 421 | − | #[derive(Debug)] | |
| 422 | − | pub struct Connection { | |
| 423 | − | pub client: MpdClient, | |
| 424 | − | pub current_song: Song, | |
| 425 | − | pub stats: Stats, | |
| 426 | − | pub status: Status, | |
| 427 | − | pub volume_status: VolumeStatus, | |
| 428 | − | last_status_update: Instant, | |
| 429 | − | } | |
| 430 | − | ||
| 431 | 410 | impl Connection { | |
| 432 | 411 | pub async fn connect(address: &str, password: Option<&str>) -> AppResult<Self> { | |
| 433 | − | let mut client = MpdClient::connect(address, password).await?; | |
| 434 | − | let status = client.status().await?; | |
| 435 | − | let stats = client.stats().await?; | |
| 436 | − | let current_song = client.current_song().await?; | |
| 437 | − | Ok(Self { | |
| 438 | − | client, | |
| 439 | − | current_song, | |
| 440 | − | stats, | |
| 441 | − | status, | |
| 442 | − | volume_status: VolumeStatus::Unmuted, | |
| 443 | − | last_status_update: Instant::now(), | |
| 444 | − | }) | |
| 412 | + | let mut connection = Self::open(address, password).await?; | |
| 413 | + | connection.update_status().await?; | |
| 414 | + | Ok(connection) | |
| 445 | 415 | } | |
| 446 | 416 | ||
| 447 | 417 | pub fn apply_snapshot(&mut self, status: Status, stats: Stats, current_song: Song) { | |
| @@ -452,9 +422,9 @@ impl Connection { | |||
| 452 | 422 | } | |
| 453 | 423 | ||
| 454 | 424 | pub async fn update_status(&mut self) -> AppResult<()> { | |
| 455 | − | self.status = self.client.status().await?; | |
| 456 | − | self.stats = self.client.stats().await?; | |
| 457 | − | self.current_song = self.client.current_song().await?; | |
| 425 | + | self.status = self.read_status().await?; | |
| 426 | + | self.stats = self.read_stats().await?; | |
| 427 | + | self.current_song = self.read_current_song().await?; | |
| 458 | 428 | self.last_status_update = Instant::now(); | |
| 459 | 429 | Ok(()) | |
| 460 | 430 | } | |
| @@ -484,21 +454,23 @@ impl Connection { | |||
| 484 | 454 | } | |
| 485 | 455 | ||
| 486 | 456 | pub async fn push(&mut self, song: &Song) -> AppResult<()> { | |
| 487 | − | self.client.add(&song.file).await?; | |
| 488 | − | let queue = self.client.queue().await?; | |
| 457 | + | self.add(&song.file).await?; | |
| 458 | + | let queue = self.queue().await?; | |
| 489 | 459 | if !queue.is_empty() { | |
| 490 | − | self.client.play(Some(queue.len() as u32 - 1)).await?; | |
| 460 | + | self.play(Some(queue.len() as u32 - 1)).await?; | |
| 491 | 461 | } | |
| 492 | 462 | self.update_status().await | |
| 493 | 463 | } | |
| 494 | 464 | ||
| 495 | 465 | pub async fn load_playlist(&mut self, playlist: &str) -> AppResult<()> { | |
| 496 | − | self.client.load_playlist(playlist).await?; | |
| 466 | + | self.append_playlist(playlist).await?; | |
| 467 | + | self.play(None).await?; | |
| 497 | 468 | self.update_status().await | |
| 498 | 469 | } | |
| 499 | 470 | ||
| 500 | 471 | pub async fn add_to_playlist(&mut self, playlist: &str, song: &Song) -> AppResult<()> { | |
| 501 | − | self.client.add_to_playlist(playlist, &song.file).await | |
| 472 | + | self.command("playlistadd", &[playlist, &song.file]).await?; | |
| 473 | + | Ok(()) | |
| 502 | 474 | } | |
| 503 | 475 | ||
| 504 | 476 | pub fn song_from_file(filename: &str) -> Song { | |
| @@ -518,41 +490,41 @@ impl Connection { | |||
| 518 | 490 | } | |
| 519 | 491 | ||
| 520 | 492 | pub async fn toggle_pause(&mut self) -> AppResult<()> { | |
| 521 | − | self.client.toggle_pause().await?; | |
| 493 | + | self.command("pause", &[]).await?; | |
| 522 | 494 | self.update_status().await | |
| 523 | 495 | } | |
| 524 | 496 | ||
| 525 | 497 | pub async fn pause(&mut self) -> AppResult<()> { | |
| 526 | − | self.client.pause(true).await?; | |
| 498 | + | self.command("pause", &["1"]).await?; | |
| 527 | 499 | self.update_status().await | |
| 528 | 500 | } | |
| 529 | 501 | ||
| 530 | 502 | pub async fn toggle_repeat(&mut self) -> AppResult<()> { | |
| 531 | − | self.client.set_repeat(!self.status.repeat).await?; | |
| 503 | + | self.set_repeat(!self.status.repeat).await?; | |
| 532 | 504 | self.update_status().await | |
| 533 | 505 | } | |
| 534 | 506 | ||
| 535 | 507 | pub async fn toggle_random(&mut self) -> AppResult<()> { | |
| 536 | − | self.client.set_random(!self.status.random).await?; | |
| 508 | + | self.set_random(!self.status.random).await?; | |
| 537 | 509 | self.update_status().await | |
| 538 | 510 | } | |
| 539 | 511 | ||
| 540 | 512 | pub async fn inc_volume(&mut self, amount: i8) -> AppResult<()> { | |
| 541 | 513 | let volume = self.status.volume.saturating_add(amount).min(100); | |
| 542 | 514 | self.volume_status = VolumeStatus::Unmuted; | |
| 543 | − | self.client.set_volume(volume).await?; | |
| 515 | + | self.set_volume(volume).await?; | |
| 544 | 516 | self.update_status().await | |
| 545 | 517 | } | |
| 546 | 518 | ||
| 547 | 519 | pub async fn dec_volume(&mut self, amount: i8) -> AppResult<()> { | |
| 548 | 520 | let volume = self.status.volume.saturating_sub(amount).max(0); | |
| 549 | 521 | self.volume_status = VolumeStatus::Unmuted; | |
| 550 | − | self.client.set_volume(volume).await?; | |
| 522 | + | self.set_volume(volume).await?; | |
| 551 | 523 | self.update_status().await | |
| 552 | 524 | } | |
| 553 | 525 | ||
| 554 | 526 | pub async fn refresh_song_list(&mut self) -> AppResult<()> { | |
| 555 | − | self.stats = self.client.stats().await?; | |
| 527 | + | self.stats = self.read_stats().await?; | |
| 556 | 528 | Ok(()) | |
| 557 | 529 | } | |
| 558 | 530 | } | |
| @@ -702,9 +674,7 @@ mod tests { | |||
| 702 | 674 | stream.get_mut().write_all(b"OK\n").await.unwrap(); | |
| 703 | 675 | }); | |
| 704 | 676 | ||
| 705 | − | let mut client = MpdClient::connect(&address.to_string(), None) | |
| 706 | − | .await | |
| 707 | − | .unwrap(); | |
| 677 | + | let mut client = Connection::open(&address.to_string(), None).await.unwrap(); | |
| 708 | 678 | client.command("play", &["0"]).await.unwrap(); | |
| 709 | 679 | server.await.unwrap(); | |
| 710 | 680 | } | |
| @@ -729,9 +699,7 @@ mod tests { | |||
| 729 | 699 | } | |
| 730 | 700 | }); | |
| 731 | 701 | ||
| 732 | − | let mut client = MpdClient::connect(&address.to_string(), None) | |
| 733 | − | .await | |
| 734 | − | .unwrap(); | |
| 702 | + | let mut client = Connection::open(&address.to_string(), None).await.unwrap(); | |
| 735 | 703 | client.ls_info("").await.unwrap(); | |
| 736 | 704 | client.list_files("").await.unwrap(); | |
| 737 | 705 | server.await.unwrap(); | |
| @@ -761,9 +729,7 @@ mod tests { | |||
| 761 | 729 | } | |
| 762 | 730 | }); | |
| 763 | 731 | ||
| 764 | − | let mut client = MpdClient::connect(&address.to_string(), None) | |
| 765 | − | .await | |
| 766 | − | .unwrap(); | |
| 732 | + | let mut client = Connection::open(&address.to_string(), None).await.unwrap(); | |
| 767 | 733 | client.delete_playlist_song("mix \"one\"", 2).await.unwrap(); | |
| 768 | 734 | client | |
| 769 | 735 | .move_playlist_song("mix \"one\"", 1, 2) | |
| @@ -45,7 +45,7 @@ pub async fn handle_key_events(key: KeyEvent, app: &mut App) -> AppResult<()> { | |||
| 45 | 45 | if is_bound(app, "quit", key) { | |
| 46 | 46 | app.quit(); | |
| 47 | 47 | } else if is_bound(app, "clear_queue", key) { | |
| 48 | − | app.conn.client.clear().await?; | |
| 48 | + | app.conn.clear().await?; | |
| 49 | 49 | app.update_queue().await?; | |
| 50 | 50 | app.conn.update_status().await?; | |
| 51 | 51 | } else if is_bound(app, "toggle_pause", key) { | |
| @@ -67,7 +67,6 @@ pub async fn handle_key_events(key: KeyEvent, app: &mut App) -> AppResult<()> { | |||
| 67 | 67 | current.saturating_sub(app.config.playback.seek_seconds) | |
| 68 | 68 | }; | |
| 69 | 69 | app.conn | |
| 70 | − | .client | |
| 71 | 70 | .seek(position.pos, std::time::Duration::from_secs(target)) | |
| 72 | 71 | .await?; | |
| 73 | 72 | app.conn.update_status().await?; | |
| @@ -86,13 +85,13 @@ pub async fn handle_key_events(key: KeyEvent, app: &mut App) -> AppResult<()> { | |||
| 86 | 85 | app.refresh_playlist_view().await?; | |
| 87 | 86 | } else if is_bound(app, "next_track", key) { | |
| 88 | 87 | if !app.queue_list.list.is_empty() { | |
| 89 | − | app.conn.client.next().await?; | |
| 88 | + | app.conn.next().await?; | |
| 90 | 89 | app.update_queue().await?; | |
| 91 | 90 | app.conn.update_status().await?; | |
| 92 | 91 | } | |
| 93 | 92 | } else if is_bound(app, "previous_track", key) { | |
| 94 | 93 | if !app.queue_list.list.is_empty() { | |
| 95 | − | app.conn.client.previous().await?; | |
| 94 | + | app.conn.previous().await?; | |
| 96 | 95 | app.update_queue().await?; | |
| 97 | 96 | app.conn.update_status().await?; | |
| 98 | 97 | } | |
| @@ -107,17 +106,17 @@ pub async fn handle_key_events(key: KeyEvent, app: &mut App) -> AppResult<()> { | |||
| 107 | 106 | } else if is_bound(app, "toggle_mute", key) { | |
| 108 | 107 | match app.conn.volume_status { | |
| 109 | 108 | VolumeStatus::Muted(volume) => { | |
| 110 | − | app.conn.client.set_volume(volume).await?; | |
| 109 | + | app.conn.set_volume(volume).await?; | |
| 111 | 110 | app.conn.volume_status = VolumeStatus::Unmuted; | |
| 112 | 111 | } | |
| 113 | 112 | VolumeStatus::Unmuted => { | |
| 114 | − | app.conn.client.set_volume(0).await?; | |
| 113 | + | app.conn.set_volume(0).await?; | |
| 115 | 114 | app.conn.volume_status = VolumeStatus::Muted(app.conn.status.volume); | |
| 116 | 115 | } | |
| 117 | 116 | } | |
| 118 | 117 | app.conn.update_status().await?; | |
| 119 | 118 | } else if is_bound(app, "update_database", key) { | |
| 120 | − | app.conn.client.update_database().await?; | |
| 119 | + | app.conn.update_database().await?; | |
| 121 | 120 | app.should_update_song_list = true; | |
| 122 | 121 | app.conn.update_status().await?; | |
| 123 | 122 | } else if is_bound(app, "search", key) { | |
| @@ -149,15 +148,12 @@ async fn handle_queue_key(key: KeyEvent, app: &mut App) -> AppResult<()> { | |||
| 149 | 148 | app.queue_list.prev(); | |
| 150 | 149 | } else if is_bound(app, "activate", key) { | |
| 151 | 150 | if app.queue_list.index < app.queue_list.list.len() { | |
| 152 | − | app.conn | |
| 153 | − | .client | |
| 154 | − | .play(Some(app.queue_list.index as u32)) | |
| 155 | − | .await?; | |
| 151 | + | app.conn.play(Some(app.queue_list.index as u32)).await?; | |
| 156 | 152 | app.conn.update_status().await?; | |
| 157 | 153 | } | |
| 158 | 154 | } else if is_bound(app, "queue_delete", key) { | |
| 159 | 155 | if app.queue_list.index < app.queue_list.list.len() { | |
| 160 | − | app.conn.client.delete(app.queue_list.index as u32).await?; | |
| 156 | + | app.conn.delete(app.queue_list.index as u32).await?; | |
| 161 | 157 | app.update_queue().await?; | |
| 162 | 158 | app.conn.update_status().await?; | |
| 163 | 159 | } | |
| @@ -165,7 +161,6 @@ async fn handle_queue_key(key: KeyEvent, app: &mut App) -> AppResult<()> { | |||
| 165 | 161 | let position = app.queue_list.index; | |
| 166 | 162 | if position + 1 < app.queue_list.list.len() { | |
| 167 | 163 | app.conn | |
| 168 | − | .client | |
| 169 | 164 | .swap(position as u32, (position + 1) as u32) | |
| 170 | 165 | .await?; | |
| 171 | 166 | app.queue_list.index += 1; | |
| @@ -175,7 +170,6 @@ async fn handle_queue_key(key: KeyEvent, app: &mut App) -> AppResult<()> { | |||
| 175 | 170 | let position = app.queue_list.index; | |
| 176 | 171 | if position > 0 && position < app.queue_list.list.len() { | |
| 177 | 172 | app.conn | |
| 178 | − | .client | |
| 179 | 173 | .swap(position as u32, (position - 1) as u32) | |
| 180 | 174 | .await?; | |
| 181 | 175 | app.queue_list.index -= 1; | |
| @@ -322,7 +316,6 @@ async fn handle_input_key(key: KeyEvent, app: &mut App) -> AppResult<()> { | |||
| 322 | 316 | if let Some(old_name) = app.pl_list.list.get(app.pl_list.index).cloned() { | |
| 323 | 317 | if !app.pl_newname_input.trim().is_empty() { | |
| 324 | 318 | app.conn | |
| 325 | − | .client | |
| 326 | 319 | .rename_playlist(&old_name, &app.pl_newname_input) | |
| 327 | 320 | .await?; | |
| 328 | 321 | app.refresh_playlists().await?; | |
| @@ -6,7 +6,7 @@ use ratatui::prelude::*; | |||
| 6 | 6 | use rmptui::{ | |
| 7 | 7 | app::App, | |
| 8 | 8 | config::Config, | |
| 9 | − | connection::{MpdClient, MpdSnapshot}, | |
| 9 | + | connection::{Connection, MpdSnapshot}, | |
| 10 | 10 | event_handler::handler, | |
| 11 | 11 | tui, AppResult, | |
| 12 | 12 | }; | |
| @@ -121,7 +121,7 @@ async fn watch_mpd( | |||
| 121 | 121 | password: Option<String>, | |
| 122 | 122 | updates: mpsc::Sender<MpdSnapshot>, | |
| 123 | 123 | ) -> AppResult<()> { | |
| 124 | − | let mut client = MpdClient::connect(&address, password.as_deref()).await?; | |
| 124 | + | let mut client = Connection::connect(&address, password.as_deref()).await?; | |
| 125 | 125 | loop { | |
| 126 | 126 | client.idle().await?; | |
| 127 | 127 | let snapshot = client.snapshot().await?; | |