Krolyxon
Commit 87f4837

rmptuisrcconnection.rs

221 lines · 6.4 KB · Rust

1use crate::app::AppResult;
2use crate::utils::is_installed;
3use mpd::song::Song;
4use mpd::{Client, State};
5use simple_dmenu::dmenu;
6use std::time::Duration;
7
8#[derive(Debug)]
9/// struct storing the mpd Client related stuff
10pub struct Connection {
11 pub conn: Client,
12 pub songs_filenames: Vec<String>,
13 pub state: String,
14 pub elapsed: Duration,
15 pub total_duration: Duration,
16 pub current_song: Song,
17 pub stats: mpd::Stats,
18 pub status: mpd::Status,
19}
20
21impl Connection {
22 /// Create a new connection
23 pub fn builder(addrs: &str) -> AppResult<Self> {
24 let mut conn = Client::connect(addrs).unwrap_or_else(|_| {
25 eprintln!("Error connecting to mpd server, Make sure mpd is running");
26 std::process::exit(1);
27 });
28
29 let empty_song = Song {
30 file: "No Song playing or in Queue".to_string(),
31 ..Default::default()
32 };
33
34 let songs_filenames: Vec<String> = conn.listall()?.into_iter().map(|x| x.file).collect();
35
36 let status = conn.status().unwrap();
37 let (elapsed, total) = status.time.unwrap_or_default();
38 let stats = conn.stats().unwrap_or_default();
39
40 let current_song = conn
41 .currentsong()
42 .unwrap_or_else(|_| Some(empty_song.clone()))
43 .unwrap_or(empty_song);
44 Ok(Self {
45 conn,
46 songs_filenames,
47 state: "Stopped".to_string(),
48 elapsed,
49 total_duration: total,
50 current_song,
51 stats,
52 status,
53 })
54 }
55
56 /// Dmenu prompt for selecting songs
57 pub fn play_dmenu(&mut self) -> AppResult<()> {
58 if is_installed("dmenu") {
59 let ss: Vec<&str> = self.songs_filenames.iter().map(|x| x.as_str()).collect();
60 let op = dmenu!(iter &ss; args "-p", "Choose a song: ", "-l", "30");
61 let index = ss.iter().position(|s| s == &op);
62 if let Some(i) = index {
63 let song = self.get_song_with_only_filename(ss.get(i).unwrap());
64 self.push(&song)?;
65 }
66 }
67
68 Ok(())
69 }
70
71 /// Update status
72 pub fn update_status(&mut self) {
73 let status = self.conn.status().unwrap();
74 let stats = self.conn.stats().unwrap_or_default();
75
76 let empty_song = self.get_song_with_only_filename("No Song playing or in Queue");
77
78 let current_song = self
79 .conn
80 .currentsong()
81 .unwrap_or_else(|_| Some(empty_song.clone()))
82 .unwrap_or(empty_song);
83
84 // Status
85 self.status = status.clone();
86
87 // Playback State
88 self.state = match status.state {
89 State::Stop => "Stopped".to_string(),
90 State::Play => "Playing".to_string(),
91 State::Pause => "Paused".to_string(),
92 };
93
94 // Progress
95 let (elapsed, total) = status.time.unwrap_or_default();
96 self.elapsed = elapsed;
97 self.total_duration = total;
98
99 // Current song
100 self.current_song = current_song;
101
102 // Stats
103 self.stats = stats;
104 }
105
106 /// Get progress ratio of current playing song
107 pub fn get_progress_ratio(&self) -> f64 {
108 let total = self.total_duration.as_secs_f64();
109 if total == 0.0 {
110 0.0
111 } else {
112 let ratio = self.elapsed.as_secs_f64() / self.total_duration.as_secs_f64();
113 if ratio > 1.0 || ratio == 0.0 {
114 0.0
115 } else {
116 ratio
117 }
118 }
119 }
120
121 /// push the given song to queue
122 pub fn push(&mut self, song: &Song) -> AppResult<()> {
123 if self.conn.queue().unwrap().is_empty() {
124 self.conn.push(song).unwrap();
125 self.conn.play().unwrap();
126 } else {
127 self.conn.push(song)?;
128 let len: u32 = (self.conn.queue().unwrap().len() - 1).try_into().unwrap();
129 self.conn.switch(len)?;
130 if self.conn.status()?.state == State::Stop {
131 self.conn.play()?;
132 }
133 }
134
135 Ok(())
136 }
137
138 /// Push all songs of a playlist into queue
139 pub fn load_playlist(&mut self, playlist: &str) -> AppResult<()> {
140 self.conn.load(playlist, ..)?;
141 self.conn.play()?;
142 Ok(())
143 }
144
145 /// Add given song to playlist
146 pub fn add_to_playlist(&mut self, playlist: &str, song: &Song) -> AppResult<()> {
147 self.conn.pl_push(playlist, song)?;
148 Ok(())
149 }
150
151 /// Given a filename, get instance of Song with only filename
152 pub fn get_song_with_only_filename(&self, filename: &str) -> Song {
153 Song {
154 file: filename.to_string(),
155 ..Default::default()
156 }
157 }
158
159 /// Given a song name from a directory, it returns the full path of the song in the database
160 pub fn get_full_path(&self, short_path: &str) -> Option<&str> {
161 for (i, f) in self.songs_filenames.iter().enumerate() {
162 if f.contains(short_path) {
163 return Some(self.songs_filenames.get(i).unwrap());
164 }
165 }
166 None
167 }
168
169 /// Gives title of current playing song
170 pub fn now_playing(&mut self) -> AppResult<Option<String>> {
171 if let Some(s) = &self.current_song.title {
172 if let Some(a) = &self.current_song.artist {
173 Ok(Some(format!("\"{}\" By {}", s, a)))
174 } else {
175 Ok(Some(s.to_string()))
176 }
177 } else {
178 Ok(Some(self.current_song.file.clone()))
179 }
180 }
181
182 // Playback controls
183 /// Pause playback
184 pub fn pause(&mut self) {
185 self.conn.pause(true).unwrap();
186 }
187
188 /// Toggles playback
189 pub fn toggle_pause(&mut self) {
190 self.conn.toggle_pause().unwrap();
191 }
192
193 /// Toggle Repeat mode
194 pub fn toggle_repeat(&mut self) {
195 let mode = self.status.repeat;
196 self.conn.repeat(!mode).unwrap();
197 }
198
199 /// Toggle random mode
200 pub fn toggle_random(&mut self) {
201 let mode = self.status.random;
202 self.conn.random(!mode).unwrap();
203 }
204
205 // Volume controls
206 /// Increase Volume
207 pub fn inc_volume(&mut self, v: i8) {
208 let cur = self.status.volume;
209 if cur + v <= 100 {
210 self.conn.volume(cur + v).unwrap();
211 }
212 }
213
214 /// Decrease volume
215 pub fn dec_volume(&mut self, v: i8) {
216 let cur = self.status.volume;
217 if cur - v >= 0 {
218 self.conn.volume(cur - v).unwrap();
219 }
220 }
221}