1use std::fmt;
2use std::path::{Path, PathBuf};
3use std::time::Duration;
4
5use eyre::WrapErr;
6use serde::Serialize;
7
8use crate::{
9 range,
10 range::INVALID_RANGE,
11 song::Current,
12 song::Finder,
13 song::Listing,
14 song::Playlist,
15 song::Playlists,
16 song::Song,
17 song::TrackList,
18 stats::Output,
19 stats::Outputs,
20 stats::Stats,
21 status::Status,
22 time, {OnOff, OutputFormat},
23};
24
25const NO_OUT: String = String::new();
26
27#[derive(PartialEq)]
28enum Direction {
29 Forward,
30 Reverse,
31}
32
33#[derive(Serialize)]
34pub struct Versions {
35 mpd: String,
36 mp_cli: String,
37}
38
39impl fmt::Display for Versions {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 write!(f, "mpd={}\nmp-cli={}", self.mpd, self.mp_cli)
42 }
43}
44
45pub struct Client {
46 client: mpd::Client,
47 format: OutputFormat,
48}
49
50impl Client {
51 pub fn new(
52 bind_to_address: &str,
53 port: &str,
54 format: Option<OutputFormat>,
55 ) -> eyre::Result<Client> {
56 let format = format.unwrap_or(OutputFormat::Json);
57 let client = mpd::Client::connect(format!("{bind_to_address}:{port}"))
58 .wrap_err("Error connecting to mpd server".to_string())?;
59
60 Ok(Self { client, format })
61 }
62
63 pub fn add(&mut self, path: &str) -> eyre::Result<Option<String>> {
67 let music_dir = self.client.music_directory()?;
68
69 let absolute_path = if path.starts_with(&music_dir) {
70 path.to_string()
71 } else {
72 PathBuf::from(&music_dir)
73 .join(path)
74 .to_str()
75 .unwrap()
76 .to_string()
77 };
78
79 let mut finder = Finder::new(music_dir);
80
81 finder.find(Path::new(Path::new(&absolute_path)))?;
82
83 for file in finder.found {
84 let song = mpd::song::Song {
85 file: file.relative_path,
86 ..Default::default()
87 };
88
89 self.client
90 .push(song.clone())
91 .wrap_err(format!("unkown or inalid path: {}", song.file))?;
92 }
93
94 Ok(None)
95 }
96
97 pub fn crop(&mut self) -> eyre::Result<Option<String>> {
98 let status = self.status()?;
102 let current_position = status.position;
103 let length = status.queue_count;
104
105 if length < 1 {
106 return self.current_status();
107 }
108
109 self.client.delete(0..current_position)?;
110 self.client.delete(1..length)?;
112
113 self.current_status()
114 }
115
116 pub fn del(
117 &mut self,
118 position: Option<u32>,
119 ) -> eyre::Result<Option<String>> {
120 let position = match position {
121 Some(position) => position,
122 None => self.status()?.position,
123 };
124
125 self.client.delete(position)?;
126
127 self.current_status()
128 }
129
130 pub fn current(&mut self) -> eyre::Result<Option<String>> {
134 let current = Current::from(self.status()?);
135
136 let response = match self.format {
137 OutputFormat::Json => serde_json::to_string(¤t)?,
138 OutputFormat::Text => current.to_string(),
139 OutputFormat::None => NO_OUT,
140 };
141
142 Ok(Some(response))
143 }
144
145 pub fn play(
146 &mut self,
147 position: Option<u32>,
148 ) -> eyre::Result<Option<String>> {
149 if position.is_none() {
150 self.client.play()?;
151 return self.current_status();
152 }
153 let position = position.unwrap();
161 let current_position = self.status()?.position;
162
163 self.pause()?;
164
165 if current_position > position {
166 for _ in (position..current_position).rev() {
167 self.prev()?;
168 }
169 } else {
170 for _ in (current_position..position).rev() {
171 self.next()?;
172 }
173 }
174
175 self.client.play()?;
176
177 self.current_status()
178 }
179
180 #[allow(clippy::should_implement_trait)]
182 pub fn next(&mut self) -> eyre::Result<Option<String>> {
183 self.client.next()?;
184
185 self.current_status()
186 }
187
188 pub fn prev(&mut self) -> eyre::Result<Option<String>> {
189 self.client.prev()?;
190
191 self.current_status()
192 }
193
194 pub fn pause(&mut self) -> eyre::Result<Option<String>> {
195 self.client.pause(true)?;
196
197 self.current_status()
198 }
199
200 pub fn pause_if_playing(&mut self) -> eyre::Result<Option<String>> {
201 match self.client.status()?.state {
202 mpd::State::Play => self.pause(),
203 mpd::State::Pause | mpd::State::Stop => Err(eyre::eyre!("")),
204 }
205 }
206
207 pub fn cdprev(&mut self) -> eyre::Result<Option<String>> {
208 let default_duration = Duration::from_secs(0);
209 let status = &self.client.status()?;
210 let current = status.elapsed.unwrap_or(default_duration).as_secs();
211
212 if current < 3 {
213 self.prev()
214 } else {
215 let place = match status.song {
216 Some(ref song) => song.pos,
217 None => 0,
218 };
219 self.client.seek(place, 0)?;
220
221 self.current_status()
222 }
223 }
224
225 pub fn toggle(&mut self) -> eyre::Result<Option<String>> {
226 match self.client.status()?.state {
227 mpd::State::Play => self.pause(),
228 mpd::State::Pause | mpd::State::Stop => self.play(None),
229 }
230 }
231
232 pub fn stop(&mut self) -> eyre::Result<Option<String>> {
233 self.client.stop()?;
234
235 self.current_status()
236 }
237
238 pub fn seek(&mut self, position: &str) -> eyre::Result<Option<String>> {
239 let current_status = self.status()?;
240
241 let place = if position.contains('%') {
243 let position = position.replace('%', "");
244
245 let percent = position.parse::<u8>().wrap_err(format!(
246 "\"{position}\" must be a value between 0 and 100"
247 ))?;
248 if percent > 100 {
249 return Err(eyre::eyre!(
250 "\"{position}\" must be a value between 0 and 100"
251 ));
252 }
253
254 let length = current_status.track_length.as_secs;
255 let percent = i64::from(percent);
256
257 length * percent / 100
258 } else if position.contains('+') || position.contains('-') {
259 current_status.elapsed.compute_offset(position)
260 } else {
261 time::Time::from(position.to_string()).as_secs
262 };
263
264 let position = self.status()?.position;
265
266 self.client.seek(position, place)?;
267
268 self.stats()
269 }
270
271 pub fn seekthrough(
272 &mut self,
273 position: &str,
274 ) -> eyre::Result<Option<String>> {
275 let mut direction = Direction::Forward;
276
277 let mut place = if position.contains('%') {
279 return Err(eyre::eyre!(
280 "seekthrough does not support percentage based seeking"
281 ));
282 } else {
283 if position.contains('-') {
285 direction = Direction::Reverse;
286 }
287 time::Time::from(position.to_string()).as_secs
288 };
289
290 let queue = self.client.queue()?;
291 let start = usize::try_from(self.status()?.position)?;
292 let mut elapsed = self.status()?.elapsed.as_secs;
293
294 match direction {
295 Direction::Forward => {
296 for song in queue.iter().cycle().skip(start) {
297 let current_song_duration =
298 i64::try_from(song.duration.unwrap().as_secs())?;
299 let remainder = current_song_duration - elapsed - place;
300
301 if remainder >= 0 {
303 let position = song.place.unwrap().id;
304 self.client.seek(position, elapsed + place)?;
305 break;
306 }
307
308 place = remainder.abs();
309 elapsed = 0;
310 }
311 }
312 Direction::Reverse => {
313 let start = queue.len() - start - 1;
315
316 for song in queue.iter().rev().cycle().skip(start) {
317 let current_song_duration =
318 i64::try_from(song.duration.unwrap().as_secs())?;
319
320 let remainder = if elapsed > 0 {
321 elapsed - place
322 } else {
323 current_song_duration - place
324 };
325
326 if remainder >= 0 {
328 let position = song.place.unwrap().id;
329 self.client.seek(position, remainder)?;
330 break;
331 }
332
333 place = remainder.abs();
334 elapsed = 0;
335 }
336 }
337 }
338
339 self.stats()
340 }
341
342 pub fn clear(&mut self) -> eyre::Result<Option<String>> {
347 self.client.clear()?;
348
349 self.current_status()
350 }
351
352 pub fn outputs(&mut self) -> eyre::Result<Option<String>> {
353 let outputs = self.client.outputs()?;
354 let outputs: Vec<Output> =
355 outputs.into_iter().map(Output::from).collect();
356 let outputs = Outputs { outputs };
357
358 let response = match self.format {
359 OutputFormat::Json => serde_json::to_string(&outputs)?,
360 OutputFormat::Text => outputs.to_string(),
361 OutputFormat::None => NO_OUT,
362 };
363
364 Ok(Some(response))
365 }
366
367 fn output_for(&mut self, name_or_id: &str) -> Result<u32, eyre::Error> {
368 let id: u32 = if let Ok(parsed_id) = name_or_id.parse::<u32>() {
369 parsed_id
370 } else {
371 self.client
372 .outputs()?
373 .iter()
374 .find(|&o| o.name == name_or_id)
375 .ok_or_else(|| eyre::eyre!("unknown output: {}", name_or_id))?
376 .id
377 };
378
379 Ok(id)
380 }
381
382 fn enable_or_disable(
383 &mut self,
384 enable: bool,
385 args: Vec<String>,
386 ) -> eyre::Result<Option<String>> {
387 let mut only = false;
388 let mut outputs = Vec::new();
389
390 for arg in args {
391 if arg == "only" {
392 only = true;
393 } else {
394 outputs.push(arg);
395 }
396 }
397
398 if only {
399 for output in self.client.outputs()? {
401 self.client.output(output, enable)?;
402 }
403 }
404
405 for name_or_id in outputs {
406 let id = self.output_for(&name_or_id)?;
407
408 self.client.output(id, enable)?;
409 }
410
411 self.outputs()
412 }
413
414 pub fn enable(
415 &mut self,
416 args: Vec<String>,
417 ) -> eyre::Result<Option<String>> {
418 self.enable_or_disable(true, args)
419 }
420
421 pub fn disable(
422 &mut self,
423 args: Vec<String>,
424 ) -> eyre::Result<Option<String>> {
425 self.enable_or_disable(false, args)
426 }
427
428 pub fn toggle_output(
429 &mut self,
430 args: Vec<String>,
431 ) -> eyre::Result<Option<String>> {
432 if args.is_empty() {
433 return Err(eyre::eyre!("no outputs given"));
434 }
435
436 for name_or_id in args {
437 let id = self.output_for(&name_or_id)?;
438
439 self.client.out_toggle(id)?;
440 }
441
442 self.outputs()
443 }
444
445 pub fn queued(&mut self) -> eyre::Result<Option<String>> {
446 if let Some(song) =
447 self.client.queue().map_err(|e| eyre::eyre!(e))?.first()
448 {
449 let current = Current::from(Song {
451 inner: song.clone(),
452 });
453
454 let response = match self.format {
455 OutputFormat::Json => serde_json::to_string(¤t)?,
456 OutputFormat::Text => current.to_string(),
457 OutputFormat::None => NO_OUT,
458 };
459
460 Ok(Some(response))
461 } else {
462 Ok(None)
463 }
464 }
465
466 pub fn shuffle(&mut self) -> eyre::Result<Option<String>> {
467 self.client.shuffle(..)?;
468
469 self.current_status()
470 }
471
472 pub fn lsplaylists(&mut self) -> eyre::Result<Option<String>> {
473 let playlists = self.client.playlists()?;
474 let playlists: Vec<Playlist> = playlists
475 .into_iter()
476 .map(|p| Playlist::from(p.name))
477 .collect();
478 let playlists = Playlists { playlists };
479
480 let response = match self.format {
481 OutputFormat::Json => serde_json::to_string(&playlists)?,
482 OutputFormat::Text => playlists.to_string(),
483 OutputFormat::None => NO_OUT,
484 };
485
486 Ok(Some(response))
487 }
488
489 pub fn load(
490 &mut self,
491 name: &String,
492 range: Option<String>,
493 ) -> eyre::Result<Option<String>> {
494 match range {
495 Some(range_str) => {
496 let range_or_index = range::Parser::new(&range_str)?;
497
498 if !range_or_index.is_range {
499 return Err(eyre::eyre!(INVALID_RANGE));
500 }
501
502 self.client.load(name, range_or_index.range)?;
503 }
504 None => {
505 self.client.load(name, ..)?;
506 }
507 }
508
509 Ok(Some(format!("loading: {name}")))
510 }
511
512 fn files_for(
514 &mut self,
515 file: Option<&str>,
516 ) -> Result<Vec<String>, eyre::Error> {
517 let all_files = Listing::from(self.client.listall()?);
518
519 let files = if let Some(ref file) = file {
520 all_files
522 .listing
523 .iter()
524 .filter(|song| song.starts_with(file))
525 .cloned()
526 .collect::<Vec<_>>()
527 } else {
528 all_files.listing.clone()
529 };
530
531 Ok(files)
532 }
533
534 pub fn insert(&mut self, uri: &str) -> eyre::Result<Option<String>> {
535 let files = self.files_for(Some(uri))?;
536
537 for file in &files {
538 let song = mpd::song::Song {
539 file: file.to_string(),
540 ..Default::default()
541 };
542
543 self.client.insert(song, 0)?;
544 }
545
546 Ok(None)
547 }
548
549 pub fn prio(
550 &mut self,
551 priority: &str,
552 position_or_range: &str,
553 ) -> eyre::Result<Option<String>> {
554 let priority = u8::try_from(priority.parse::<u32>()?).wrap_err(
555 format!("\"{priority}\" must be a value between 0 and 255"),
556 )?;
557
558 let queue_size = u32::try_from(self.client.queue()?.len())?;
559 let position_or_range = range::Parser::new(position_or_range)?;
560
561 if position_or_range.index > queue_size {
562 return Err(eyre::eyre!(
563 "position ({}) must be less than or equal to the queue length {}",
564 position_or_range.index,
565 queue_size,
566 ));
567 }
568
569 if position_or_range.is_range {
570 self.client.priority(position_or_range.range, priority)?;
571 } else {
572 self.client.priority(position_or_range.index, priority)?;
573 };
574
575 Ok(None)
576 }
577
578 pub fn playlist(
579 &mut self,
580 name: Option<String>,
581 ) -> eyre::Result<Option<String>> {
582 let songs = match name {
585 Some(name) => self.client.playlist(&name)?,
586 None => self.client.queue()?,
587 };
588
589 let songs: Vec<Current> = songs
590 .into_iter()
591 .map(|s| Current::from(Song { inner: s }))
592 .collect();
593 let track_list = TrackList { songs };
594
595 let response = match self.format {
596 OutputFormat::Json => serde_json::to_string(&track_list)?,
597 OutputFormat::Text => track_list.to_string(),
598 OutputFormat::None => NO_OUT,
599 };
600
601 Ok(Some(response))
602 }
603
604 pub fn listall(
605 &mut self,
606 file: Option<&str>,
607 ) -> eyre::Result<Option<String>> {
608 let files = Listing::from(self.files_for(file)?);
609
610 let response = match self.format {
611 OutputFormat::Json => serde_json::to_string(&files)?,
612 OutputFormat::Text => files.to_string(),
613 OutputFormat::None => NO_OUT,
614 };
615
616 Ok(Some(response))
617 }
618
619 pub fn ls(
620 &mut self,
621 directory: Option<&str>,
622 ) -> eyre::Result<Option<String>> {
623 let directory = directory.unwrap_or("");
624 let listing = self.client.listfiles(directory)?;
625 let filter_for = if let Some(entry) = listing.first() {
626 entry.0.as_str()
627 } else {
628 "directory"
629 };
630
631 let results = Listing::from(
632 listing
633 .clone()
634 .into_iter()
635 .filter(|(key, _)| key == filter_for)
636 .map(|(_, value)| {
637 PathBuf::from(&directory)
638 .join(value)
639 .to_str()
640 .unwrap()
641 .to_string()
642 })
643 .collect::<Vec<String>>(),
644 );
645
646 let response = match self.format {
647 OutputFormat::Json => serde_json::to_string(&results)?,
648 OutputFormat::Text => results.to_string(),
649 OutputFormat::None => NO_OUT,
650 };
651
652 Ok(Some(response))
653 }
654
655 pub fn repeat(
656 &mut self,
657 state: Option<OnOff>,
658 ) -> eyre::Result<Option<String>> {
659 let state = match state {
660 Some(state) => state == OnOff::On,
661 None => !self.client.status()?.repeat,
662 };
663
664 self.client.repeat(state)?;
665
666 self.current_status()
667 }
668
669 pub fn random(
670 &mut self,
671 state: Option<OnOff>,
672 ) -> eyre::Result<Option<String>> {
673 let state = match state {
674 Some(state) => state == OnOff::On,
675 None => !self.client.status()?.random,
676 };
677
678 self.client.random(state)?;
679
680 self.current_status()
681 }
682
683 pub fn single(
684 &mut self,
685 state: Option<OnOff>,
686 ) -> eyre::Result<Option<String>> {
687 let state = match state {
688 Some(state) => state == OnOff::On,
689 None => !self.client.status()?.single,
690 };
691
692 self.client.single(state)?;
693
694 self.current_status()
695 }
696
697 pub fn _search(
699 &mut self,
700 tag: &str,
701 query: &str,
702 ) -> eyre::Result<Vec<mpd::Song>> {
703 let term = mpd::Term::Tag(tag.into());
704 let mut binding = mpd::Query::new();
705 let songs = binding.and(term, query);
706
707 Ok(self.client.search(songs, None)?)
708 }
709
710 pub fn _find(
712 &mut self,
713 tag: &str,
714 query: &str,
715 ) -> eyre::Result<Vec<mpd::Song>> {
716 let term = mpd::Term::Tag(tag.into());
717 let mut binding = mpd::Query::new();
718 let songs = binding.and(term, query);
719
720 Ok(self.client.search(songs, None)?)
721 }
722
723 pub fn search(
724 &mut self,
725 tag: &str,
726 query: &str,
727 ) -> eyre::Result<Option<String>> {
728 let songs = self._search(tag, query)?;
729 let files = Listing::from(songs);
730
731 let response = match self.format {
732 OutputFormat::Json => serde_json::to_string(&files)?,
733 OutputFormat::Text => files.to_string(),
734 OutputFormat::None => NO_OUT,
735 };
736
737 Ok(Some(response))
738 }
739
740 pub fn search_add(
741 &mut self,
742 tag: &str,
743 query: &str,
744 ) -> eyre::Result<Option<String>> {
745 let songs = self._search(tag, query)?;
746
747 for song in songs {
748 self.client
749 .push(song.clone())
750 .wrap_err(format!("unkown or inalid path: {}", song.file))?;
751 }
752
753 Ok(None)
754 }
755
756 pub fn find(
757 &mut self,
758 tag: &str,
759 query: &str,
760 ) -> eyre::Result<Option<String>> {
761 let songs = self._find(tag, query)?;
762 let files = Listing::from(songs);
763
764 let response = match self.format {
765 OutputFormat::Json => serde_json::to_string(&files)?,
766 OutputFormat::Text => files.to_string(),
767 OutputFormat::None => NO_OUT,
768 };
769
770 Ok(Some(response))
771 }
772
773 pub fn find_add(
774 &mut self,
775 tag: &str,
776 query: &str,
777 ) -> eyre::Result<Option<String>> {
778 let songs = self._find(tag, query)?;
779
780 for song in songs {
781 self.client
782 .push(song.clone())
783 .wrap_err(format!("unkown or inalid path: {}", song.file))?;
784 }
785
786 Ok(None)
787 }
788
789 pub fn list(&mut self, tag: &str) -> eyre::Result<Option<String>> {
790 let term = mpd::Term::Tag(tag.into());
791 let query = mpd::Query::new();
792
793 let results = self.client.list(&term, &query)?;
794 let files = Listing::from(results);
795
796 let response = match self.format {
797 OutputFormat::Json => serde_json::to_string(&files)?,
798 OutputFormat::Text => files.to_string(),
799 OutputFormat::None => NO_OUT,
800 };
801
802 Ok(Some(response))
803 }
804
805 pub fn consume(
806 &mut self,
807 state: Option<OnOff>,
808 ) -> eyre::Result<Option<String>> {
809 let state = match state {
810 Some(state) => state == OnOff::On,
811 None => !self.client.status()?.consume,
812 };
813
814 self.client.consume(state)?;
815
816 self.current_status()
817 }
818
819 pub fn crossfade(
820 &mut self,
821 seconds: Option<String>,
822 ) -> eyre::Result<Option<String>> {
823 let crossfade = match seconds {
824 Some(secs) => secs.parse::<i64>().wrap_err(format!(
825 "\"{secs}\" is not 0 or a positive number"
826 ))?,
827 None => 0,
828 };
829
830 self.client
831 .crossfade(crossfade)
832 .wrap_err(format!("\"{crossfade}\" is too large"))?;
833
834 Ok(Some(format!("crossfade: {crossfade}")))
835 }
836
837 pub fn version(&mut self) -> eyre::Result<Option<String>> {
838 let mpd = format!(
839 "{}.{}.{}",
840 self.client.version.0, self.client.version.1, self.client.version.2
841 );
842 let mp_cli = env!("CARGO_PKG_VERSION").to_string();
843
844 let versions = Versions { mpd, mp_cli };
845
846 let response = match self.format {
847 OutputFormat::Json => serde_json::to_string(&versions)?,
848 OutputFormat::Text => versions.to_string(),
849 OutputFormat::None => NO_OUT,
850 };
851
852 Ok(Some(response))
853 }
854
855 pub fn stats(&mut self) -> eyre::Result<Option<String>> {
856 let stats = Stats::new(self.client.stats()?);
857
858 let response = match self.format {
859 OutputFormat::Json => serde_json::to_string(&stats)?,
860 OutputFormat::Text => stats.to_string(),
861 OutputFormat::None => NO_OUT,
862 };
863
864 Ok(Some(response))
865 }
866
867 pub fn save(&mut self, name: &str) -> eyre::Result<Option<String>> {
868 self.client
869 .save(name)
870 .wrap_err(format!("Playlist already exists: {name}"))?;
871
872 Ok(None)
873 }
874
875 pub fn rm(&mut self, name: &str) -> eyre::Result<Option<String>> {
876 self.client
877 .pl_remove(name)
878 .wrap_err(format!("Unknown playlist: {name}"))?;
879
880 Ok(None)
881 }
882
883 pub fn set_volume(&mut self, input: &str) -> eyre::Result<Option<String>> {
888 let current = self.client.status()?.volume;
889
890 let target = match input {
891 matched if matched.starts_with('+') => {
892 if let Ok(volume) = matched[1..].parse::<i8>() {
893 current.checked_add(volume).unwrap_or(100).min(100)
894 } else {
895 panic!("Invalid volume increment, must be between 1-100")
896 }
897 }
898 matched if matched.starts_with('-') => {
899 if let Ok(volume) = matched[1..].parse::<i8>() {
900 current.checked_sub(volume).unwrap_or(100).max(0)
901 } else {
902 panic!("Invalid volume increment, must be between 1-100")
903 }
904 }
905 _ => input.parse::<i8>().unwrap_or(0),
906 };
907
908 self.client
909 .volume(target)
910 .map(|()| None)
911 .map_err(eyre::Report::from)
912 }
913
914 pub fn status(&mut self) -> eyre::Result<Status> {
919 let status = self.client.status()?;
920
921 let volume = status.volume.to_string();
922
923 let current_song = self.client.currentsong()?;
924
925 let artist = current_song
926 .as_ref()
927 .and_then(|song| song.artist.as_ref())
928 .map_or(String::new(), ToString::to_string);
929
930 let album = current_song
931 .as_ref()
932 .and_then(|song| {
933 song.tags
934 .iter()
935 .find(|&(key, _)| key.to_lowercase() == "album")
936 })
937 .map_or_else(String::new, |(_, value)| value.clone());
938
939 let title = current_song
940 .as_ref()
941 .and_then(|song| song.title.as_ref())
942 .map_or(String::new(), ToString::to_string);
943
944 let position = match status.song {
945 Some(song) => song.pos,
946 None => 0,
947 };
948
949 let time = crate::time::Track::from(status.time);
950
951 let file_path =
952 self.client.currentsong()?.map(|song| song.file.clone());
953
954 Ok(Status {
955 volume,
956 state: crate::status::State::from(status.state),
957 artist,
958 album,
959 title,
960 position,
961 queue_count: status.queue_len,
962 elapsed: time.elapsed,
963 track_length: time.total,
964 repeat: OnOff::from(status.repeat),
965 random: OnOff::from(status.random),
966 single: OnOff::from(status.single),
967 consume: OnOff::from(status.consume),
968 file_path,
969 })
970 }
971
972 pub fn current_status(&mut self) -> eyre::Result<Option<String>> {
973 let status = self.status()?;
974 let response = match self.format {
975 OutputFormat::Json => serde_json::to_string(&status)?,
976 OutputFormat::Text => format!("{status}"),
977 OutputFormat::None => NO_OUT,
978 };
979
980 Ok(Some(response))
981 }
982}