Krolyxon
Commit 51fbf42

rmptuisrcconnection.rs

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