Krolyxon
Commit b9d6169

rmptuisrcconnection.rs

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