Krolyxon
Commit 7eac9cf

rmptuisrcconnection.rs

311 lines · 9.0 KB · Rust

1use mpd::song::Song;
2use mpd::{Client, State};
3use simple_dmenu::dmenu;
4use std::process::Command;
5use std::time::Duration;
6
7use crate::app::AppResult;
8
9pub type Result<T> = core::result::Result<T, Error>;
10pub type Error = Box<dyn std::error::Error>;
11
12#[derive(Debug)]
13pub struct Connection {
14 pub conn: Client,
15 pub songs_filenames: Vec<String>,
16 pub state: String,
17 pub elapsed: Duration,
18 pub total_duration: Duration,
19 pub volume: u8,
20 pub repeat: bool,
21 pub random: bool,
22 pub current_song: Song,
23}
24
25impl Connection {
26 /// Create a new connection
27 pub fn new(addrs: &str) -> Result<Self> {
28 let mut conn = Client::connect(addrs).unwrap();
29
30 let empty_song = Song {
31 file: "No Song playing or in Queue".to_string(),
32 artist: None,
33 title: None,
34 duration: None,
35 last_mod: None,
36 name: None,
37 place: None,
38 range: None,
39 tags: vec![("".to_string(), "".to_string())],
40 };
41
42 let songs_filenames: Vec<String> = conn
43 .listall()
44 .unwrap()
45 .into_iter()
46 .map(|x| x.file)
47 .collect();
48
49 let status = conn.status().unwrap();
50 let (elapsed, total) = status.time.unwrap_or_default();
51 let volume: u8 = status.volume as u8;
52 let repeat = status.repeat;
53 let random = status.random;
54
55 let current_song = conn
56 .currentsong()
57 .unwrap_or_else(|_| Some(empty_song.clone()))
58 .unwrap_or_else(|| empty_song);
59 Ok(Self {
60 conn,
61 songs_filenames,
62 state: "Stopped".to_string(),
63 elapsed,
64 total_duration: total,
65 volume,
66 repeat,
67 random,
68 current_song,
69 })
70 }
71
72 /// Fzf prompt for selecting song
73 pub fn play_fzf(&mut self) -> Result<()> {
74 is_installed("fzf").map_err(|ex| ex)?;
75
76 let ss = &self.songs_filenames;
77 let fzf_choice = rust_fzf::select(ss.clone(), Vec::new()).unwrap();
78 let index = get_choice_index(&self.songs_filenames, fzf_choice.get(0).unwrap());
79 let song = self.get_song_with_only_filename(ss.get(index).unwrap());
80 self.push(&song)?;
81
82 Ok(())
83 }
84
85 /// Dmenu prompt for selecting songs
86 pub fn play_dmenu(&mut self) -> Result<()> {
87 is_installed("dmenu").map_err(|ex| ex)?;
88 let ss: Vec<&str> = self.songs_filenames.iter().map(|x| x.as_str()).collect();
89 let op = dmenu!(iter &ss; args "-p", "Choose a song: ", "-l", "30");
90 let index = get_choice_index(&self.songs_filenames, &op);
91 let song = self.get_song_with_only_filename(ss.get(index).unwrap());
92 self.push(&song)?;
93 Ok(())
94 }
95
96 /// Update status
97 pub fn update_status(&mut self) {
98 let status = self.conn.status().unwrap();
99 let empty_song = self.get_song_with_only_filename("No Song playing or in Queue");
100 let current_song = self
101 .conn
102 .currentsong()
103 .unwrap_or_else(|_| Some(empty_song.clone()))
104 .unwrap_or_else(|| empty_song);
105
106 // Playback State
107 match status.state {
108 State::Stop => self.state = "Stopped".to_string(),
109 State::Play => self.state = "Playing".to_string(),
110 State::Pause => self.state = "Paused".to_string(),
111 }
112
113 // Progress
114 let (elapsed, total) = status.time.unwrap_or_default();
115 self.elapsed = elapsed;
116 self.total_duration = total;
117
118 // Volume
119 self.volume = status.volume as u8;
120
121 // Repeat mode
122 self.repeat = status.repeat;
123
124 // Random mode
125 self.random = status.random;
126
127 self.current_song = current_song;
128 }
129
130 /// Get progress ratio of current playing song
131 pub fn get_progress_ratio(&self) -> f64 {
132 let total = self.total_duration.as_secs_f64();
133 if total == 0.0 {
134 0.0
135 } else {
136 let ratio = self.elapsed.as_secs_f64() / self.total_duration.as_secs_f64();
137 if ratio > 1.0 || ratio == 0.0 {
138 0.0
139 } else {
140 ratio
141 }
142 }
143 }
144
145 /// push the given song to queue
146 pub fn push(&mut self, song: &Song) -> Result<()> {
147 if self.conn.queue().unwrap().is_empty() {
148 self.conn.push(song).unwrap();
149 self.conn.play().unwrap();
150 } else {
151 self.conn.push(song)?;
152 let len: u32 = (self.conn.queue().unwrap().len() - 1).try_into().unwrap();
153 self.conn.switch(len)?;
154 if self.conn.status()?.state == State::Stop {
155 self.conn.play()?;
156 }
157 }
158
159 Ok(())
160 }
161
162 /// Push all songs of a playlist into queue
163 pub fn push_playlist(&mut self, playlist: &str) -> Result<()> {
164 let songs: Vec<Song> = self.conn.playlist(playlist)?;
165
166 for song in songs {
167 if self.songs_filenames.contains(&song.file) {
168 let song = self.get_song_with_only_filename(&song.file);
169 self.conn.push(&song)?;
170 self.conn.play()?;
171 }
172 }
173 Ok(())
174 }
175
176 /// Add given song to playlist
177 pub fn add_to_playlist(&mut self, playlist: &str, song: &Song) -> Result<()> {
178 self.conn.pl_push(playlist, song)?;
179 Ok(())
180 }
181
182 /// Given a filename, get instance of Song with only filename
183 pub fn get_song_with_only_filename(&self, filename: &str) -> Song {
184 Song {
185 file: filename.to_string(),
186 artist: None,
187 title: None,
188 duration: None,
189 last_mod: None,
190 name: None,
191 place: None,
192 range: None,
193 tags: vec![("".to_string(), "".to_string())],
194 }
195 }
196
197 /// Given a song name from a directory, it returns the full path of the song in the database
198 pub fn get_full_path(&self, short_path: &str) -> AppResult<String> {
199 let list = self
200 .songs_filenames
201 .iter()
202 .map(|f| f.as_str())
203 .collect::<Vec<&str>>();
204 let (filename, _) = rust_fuzzy_search::fuzzy_search_sorted(&short_path, &list)
205 .get(0)
206 .unwrap()
207 .clone();
208
209 Ok(filename.to_string())
210 }
211
212 /// Print status to stdout
213 pub fn status(&mut self) {
214 let current_song = self.conn.currentsong();
215 let status = self.conn.status().unwrap();
216
217 if current_song.is_ok() && status.state != State::Stop {
218 let song = current_song.unwrap();
219 if let Some(s) = song {
220 println!("{} - {}", s.artist.unwrap(), s.title.unwrap());
221 }
222 }
223 println!(
224 "volume: {}\trepeat: {}\trandom: {}\tsingle: {}\tconsume: {}",
225 status.volume, status.repeat, status.random, status.single, status.consume
226 );
227 }
228
229 /// Gives title of current playing song
230 pub fn now_playing(&mut self) -> Result<Option<String>> {
231 if let Some(s) = &self.current_song.title {
232 if let Some(a) = &self.current_song.artist {
233 return Ok(Some(format!("\"{}\" By {}", s, a)));
234 } else {
235 return Ok(Some(s.to_string()));
236 }
237 } else {
238 return Ok(Some(self.current_song.file.clone()));
239 }
240 }
241
242 // Playback controls
243 /// Pause playback
244 pub fn pause(&mut self) {
245 self.conn.pause(true).unwrap();
246 }
247
248 /// Toggles playback
249 pub fn toggle_pause(&mut self) {
250 self.conn.toggle_pause().unwrap();
251 }
252
253 /// Toggle Repeat mode
254 pub fn toggle_repeat(&mut self) {
255 if self.conn.status().unwrap().repeat {
256 self.conn.repeat(false).unwrap();
257 } else {
258 self.conn.repeat(true).unwrap();
259 }
260 }
261
262 /// Toggle random mode
263 pub fn toggle_random(&mut self) {
264 if self.conn.status().unwrap().random {
265 self.conn.random(false).unwrap();
266 } else {
267 self.conn.random(true).unwrap();
268 }
269 }
270
271 // Volume controls
272 /// Increase Volume
273 pub fn inc_volume(&mut self, v: i8) {
274 let cur = self.conn.status().unwrap().volume;
275 if cur + v <= 100 {
276 self.conn.volume(cur + v).unwrap();
277 }
278 }
279
280 /// Decrease volume
281 pub fn dec_volume(&mut self, v: i8) {
282 let cur = self.conn.status().unwrap().volume;
283 if cur - v >= 0 {
284 self.conn.volume(cur - v).unwrap();
285 }
286 }
287}
288
289/// Gets the index of the string from the Vector
290fn get_choice_index(ss: &Vec<String>, selection: &str) -> usize {
291 let mut choice: usize = 0;
292 if let Some(index) = ss.iter().position(|s| s == selection) {
293 choice = index;
294 }
295
296 choice
297}
298
299/// Checks if given program is installed in your system
300fn is_installed(ss: &str) -> Result<()> {
301 let output = Command::new("which")
302 .arg(ss)
303 .output()
304 .expect("Failed to execute command");
305 if output.status.success() {
306 Ok(())
307 } else {
308 let err = format!("{} not installed", ss);
309 Err(err.into())
310 }
311}