mpd_client/commands/
definitions.rs

1//! Definitions of commands.
2
3use std::{
4    cmp::min,
5    fmt::Write,
6    ops::{Bound, RangeBounds},
7    time::Duration,
8};
9
10use bytes::BytesMut;
11use mpd_protocol::{
12    command::{Argument, Command as RawCommand},
13    response::Frame,
14};
15
16use crate::{
17    commands::{Command, ReplayGainMode, SeekMode, SingleMode, Song, SongId, SongPosition},
18    filter::Filter,
19    responses::{self as res, value, TypedResponseError},
20    tag::Tag,
21};
22
23macro_rules! argless_command {
24    // Utility branch to generate struct with doc expression
25    (#[doc = $doc:expr],
26     $item:item) => {
27        #[doc = $doc]
28        #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
29        $item
30    };
31    ($name:ident, $command:literal) => {
32        argless_command!(
33            #[doc = concat!("`", $command, "` command.")],
34            pub struct $name;
35        );
36
37        impl Command for $name {
38            type Response = ();
39
40            fn command(&self) -> RawCommand {
41                RawCommand::new($command)
42            }
43
44            fn response(self, _: Frame) -> Result<Self::Response, TypedResponseError> {
45                Ok(())
46            }
47        }
48    };
49}
50
51macro_rules! single_arg_command {
52    // Utility branch to generate struct with doc expression
53    (#[doc = $doc:expr],
54     $item:item) => {
55        #[doc = $doc]
56        #[derive(Clone, Debug, PartialEq, Eq)]
57        #[allow(missing_copy_implementations)]
58        $item
59    };
60    ($name:ident $(<$lt:lifetime>)?, $argtype:ty, $command:literal) => {
61        single_arg_command!(
62            #[doc = concat!("`", $command, "` command.")],
63            pub struct $name $(<$lt>)? (pub $argtype);
64        );
65
66        impl $(<$lt>)? Command for $name $(<$lt>)? {
67            type Response = ();
68
69            fn command(&self) -> RawCommand {
70                RawCommand::new($command)
71                    .argument(&self.0)
72            }
73
74            fn response(self, _: Frame) -> Result<Self::Response, TypedResponseError> {
75                Ok(())
76            }
77        }
78    };
79}
80
81argless_command!(ClearQueue, "clear");
82argless_command!(Next, "next");
83argless_command!(Ping, "ping");
84argless_command!(Previous, "previous");
85argless_command!(Stop, "stop");
86
87single_arg_command!(ClearPlaylist<'a>, &'a str, "playlistclear");
88single_arg_command!(DeletePlaylist<'a>, &'a str, "rm");
89single_arg_command!(SaveQueueAsPlaylist<'a>, &'a str, "save");
90single_arg_command!(SetConsume, bool, "consume");
91single_arg_command!(SetPause, bool, "pause");
92single_arg_command!(SetRandom, bool, "random");
93single_arg_command!(SetRepeat, bool, "repeat");
94single_arg_command!(SubscribeToChannel<'a>, &'a str, "subscribe");
95single_arg_command!(UnsubscribeFromChannel<'a>, &'a str, "unsubscribe");
96
97/// `replay_gain_status` command.
98#[derive(Clone, Copy, Debug, PartialEq, Eq)]
99pub struct ReplayGainStatus;
100
101impl Command for ReplayGainStatus {
102    type Response = res::ReplayGainStatus;
103
104    fn command(&self) -> RawCommand {
105        RawCommand::new("replay_gain_status")
106    }
107
108    fn response(self, frame: Frame) -> Result<Self::Response, TypedResponseError> {
109        res::ReplayGainStatus::from_frame(frame)
110    }
111}
112
113/// `status` command.
114#[derive(Clone, Copy, Debug, PartialEq, Eq)]
115pub struct Status;
116
117impl Command for Status {
118    type Response = res::Status;
119
120    fn command(&self) -> RawCommand {
121        RawCommand::new("status")
122    }
123
124    fn response(self, frame: Frame) -> Result<Self::Response, TypedResponseError> {
125        res::Status::from_frame(frame)
126    }
127}
128
129/// `stats` command.
130#[derive(Clone, Copy, Debug, PartialEq, Eq)]
131pub struct Stats;
132
133impl Command for Stats {
134    type Response = res::Stats;
135
136    fn command(&self) -> RawCommand {
137        RawCommand::new("stats")
138    }
139
140    fn response(self, frame: Frame) -> Result<Self::Response, TypedResponseError> {
141        res::Stats::from_frame(frame)
142    }
143}
144
145/// `playlistinfo` command.
146#[derive(Clone, Copy, Debug, PartialEq, Eq)]
147pub struct Queue;
148
149impl Command for Queue {
150    type Response = Vec<res::SongInQueue>;
151
152    fn command(&self) -> RawCommand {
153        RawCommand::new("playlistinfo")
154    }
155
156    fn response(self, frame: Frame) -> Result<Self::Response, TypedResponseError> {
157        res::SongInQueue::from_frame_multi(frame)
158    }
159}
160
161impl Queue {
162    /// Get the metadata about the entire queue.
163    pub fn all() -> Queue {
164        Queue
165    }
166
167    /// Get the metadata for a specific song in the queue.
168    pub fn song<S>(song: S) -> QueueRange
169    where
170        S: Into<Song>,
171    {
172        QueueRange(SongOrSongRange::Single(song.into()))
173    }
174
175    /// Get the metadata for a range of songs in the queue.
176    pub fn range<R>(range: R) -> QueueRange
177    where
178        R: RangeBounds<SongPosition>,
179    {
180        QueueRange(SongOrSongRange::Range(SongRange::new(range)))
181    }
182}
183
184#[derive(Clone, Copy, Debug, PartialEq, Eq)]
185struct SongRange {
186    from: usize,
187    to: Option<usize>,
188}
189
190impl SongRange {
191    fn new_usize<R: RangeBounds<usize>>(range: R) -> Self {
192        let from = match range.start_bound() {
193            Bound::Excluded(pos) => pos.saturating_add(1),
194            Bound::Included(pos) => *pos,
195            Bound::Unbounded => 0,
196        };
197
198        let to = match range.end_bound() {
199            Bound::Excluded(pos) => Some(*pos),
200            Bound::Included(pos) => Some(pos.saturating_add(1)),
201            Bound::Unbounded => None,
202        };
203
204        Self { from, to }
205    }
206
207    fn new<R: RangeBounds<SongPosition>>(range: R) -> Self {
208        let from = match range.start_bound() {
209            Bound::Excluded(pos) => Bound::Excluded(pos.0),
210            Bound::Included(pos) => Bound::Included(pos.0),
211            Bound::Unbounded => Bound::Unbounded,
212        };
213
214        let to = match range.end_bound() {
215            Bound::Excluded(pos) => Bound::Excluded(pos.0),
216            Bound::Included(pos) => Bound::Included(pos.0),
217            Bound::Unbounded => Bound::Unbounded,
218        };
219
220        Self::new_usize((from, to))
221    }
222}
223
224impl Argument for SongRange {
225    fn render(&self, buf: &mut BytesMut) {
226        if let Some(to) = self.to {
227            write!(buf, "{}:{}", self.from, to).unwrap();
228        } else {
229            write!(buf, "{}:", self.from).unwrap();
230        }
231    }
232}
233
234#[derive(Clone, Copy, Debug, PartialEq, Eq)]
235enum SongOrSongRange {
236    /// Single Song
237    Single(Song),
238
239    /// Song Range
240    Range(SongRange),
241}
242
243/// `playlistinfo` / `playlistid` commands.
244///
245/// These return the metadata of specific individual songs or subranges of the queue.
246#[derive(Clone, Copy, Debug, PartialEq, Eq)]
247pub struct QueueRange(SongOrSongRange);
248
249impl QueueRange {
250    /// Get the metadata for a specific song in the queue.
251    pub fn song<S>(song: S) -> Self
252    where
253        S: Into<Song>,
254    {
255        Self(SongOrSongRange::Single(song.into()))
256    }
257
258    /// Get the metadata for a range of songs in the queue.
259    pub fn range<R>(range: R) -> Self
260    where
261        R: RangeBounds<SongPosition>,
262    {
263        Self(SongOrSongRange::Range(SongRange::new(range)))
264    }
265}
266
267impl Command for QueueRange {
268    type Response = Vec<res::SongInQueue>;
269
270    fn command(&self) -> RawCommand {
271        match self.0 {
272            SongOrSongRange::Single(Song::Id(id)) => RawCommand::new("playlistid").argument(id),
273            SongOrSongRange::Single(Song::Position(pos)) => {
274                RawCommand::new("playlistinfo").argument(pos)
275            }
276            SongOrSongRange::Range(range) => RawCommand::new("playlistinfo").argument(range),
277        }
278    }
279
280    fn response(self, frame: Frame) -> Result<Self::Response, TypedResponseError> {
281        res::SongInQueue::from_frame_multi(frame)
282    }
283}
284
285/// `currentsong` command.
286#[derive(Clone, Copy, Debug, PartialEq, Eq)]
287pub struct CurrentSong;
288
289impl Command for CurrentSong {
290    type Response = Option<res::SongInQueue>;
291
292    fn command(&self) -> RawCommand {
293        RawCommand::new("currentsong")
294    }
295
296    fn response(self, frame: Frame) -> Result<Self::Response, TypedResponseError> {
297        res::SongInQueue::from_frame_single(frame)
298    }
299}
300
301/// `listplaylists` command.
302#[derive(Clone, Copy, Debug, PartialEq, Eq)]
303pub struct GetPlaylists;
304
305impl Command for GetPlaylists {
306    type Response = Vec<res::Playlist>;
307
308    fn command(&self) -> RawCommand {
309        RawCommand::new("listplaylists")
310    }
311
312    fn response(self, frame: Frame) -> Result<Self::Response, TypedResponseError> {
313        res::Playlist::parse_frame(frame)
314    }
315}
316
317/// `tagtypes` command.
318#[derive(Clone, Copy, Debug, PartialEq, Eq)]
319pub struct GetEnabledTagTypes;
320
321impl Command for GetEnabledTagTypes {
322    type Response = Vec<Tag>;
323
324    fn command(&self) -> RawCommand {
325        RawCommand::new("tagtypes")
326    }
327
328    fn response(self, frame: Frame) -> Result<Self::Response, TypedResponseError> {
329        let mut out = Vec::with_capacity(frame.fields_len());
330        for (key, value) in frame {
331            if &*key != "tagtype" {
332                return Err(TypedResponseError::unexpected_field(
333                    "tagtype",
334                    key.as_ref(),
335                ));
336            }
337
338            let tag = Tag::try_from(&*value)
339                .map_err(|e| TypedResponseError::invalid_value("tagtype", value).source(e))?;
340
341            out.push(tag);
342        }
343
344        Ok(out)
345    }
346}
347
348/// `listplaylistinfo` command.
349#[derive(Clone, Debug, PartialEq, Eq)]
350pub struct GetPlaylist<'a>(pub &'a str);
351
352impl<'a> Command for GetPlaylist<'a> {
353    type Response = Vec<res::Song>;
354
355    fn command(&self) -> RawCommand {
356        RawCommand::new("listplaylistinfo").argument(self.0)
357    }
358
359    fn response(self, frame: Frame) -> Result<Self::Response, TypedResponseError> {
360        res::Song::from_frame_multi(frame)
361    }
362}
363
364/// `setvol` command.
365///
366/// Set the volume. The value is truncated to fit in the range `0..=100`.
367#[derive(Clone, Copy, Debug, PartialEq, Eq)]
368pub struct SetVolume(pub u8);
369
370impl Command for SetVolume {
371    type Response = ();
372
373    fn command(&self) -> RawCommand {
374        let volume = min(self.0, 100);
375        RawCommand::new("setvol").argument(volume)
376    }
377
378    fn response(self, _: Frame) -> Result<Self::Response, TypedResponseError> {
379        Ok(())
380    }
381}
382
383/// `single` command.
384#[derive(Clone, Copy, Debug, PartialEq, Eq)]
385pub struct SetSingle(pub SingleMode);
386
387impl Command for SetSingle {
388    type Response = ();
389
390    fn command(&self) -> RawCommand {
391        let single = match self.0 {
392            SingleMode::Disabled => "0",
393            SingleMode::Enabled => "1",
394            SingleMode::Oneshot => "oneshot",
395        };
396
397        RawCommand::new("single").argument(single)
398    }
399
400    fn response(self, _: Frame) -> Result<Self::Response, TypedResponseError> {
401        Ok(())
402    }
403}
404
405/// 'replay_gain_mode' command
406#[derive(Clone, Copy, Debug, PartialEq, Eq)]
407pub struct SetReplayGainMode(pub ReplayGainMode);
408
409impl Command for SetReplayGainMode {
410    type Response = ();
411
412    fn command(&self) -> RawCommand {
413        let rgm = match self.0 {
414            ReplayGainMode::Off => "off",
415            ReplayGainMode::Track => "track",
416            ReplayGainMode::Album => "album",
417            ReplayGainMode::Auto => "auto",
418        };
419
420        RawCommand::new("replay_gain_mode").argument(rgm)
421    }
422
423    fn response(self, _: Frame) -> Result<Self::Response, TypedResponseError> {
424        Ok(())
425    }
426}
427
428/// `crossfade` command.
429///
430/// The given duration is rounded down to whole seconds.
431#[derive(Clone, Copy, Debug, PartialEq, Eq)]
432pub struct Crossfade(pub Duration);
433
434impl Command for Crossfade {
435    type Response = ();
436
437    fn command(&self) -> RawCommand {
438        let seconds = self.0.as_secs();
439        RawCommand::new("crossfade").argument(seconds)
440    }
441
442    fn response(self, _: Frame) -> Result<Self::Response, TypedResponseError> {
443        Ok(())
444    }
445}
446
447/// `seek` and `seekid` commands.
448#[derive(Clone, Copy, Debug, PartialEq, Eq)]
449pub struct SeekTo(pub Song, pub Duration);
450
451impl Command for SeekTo {
452    type Response = ();
453
454    fn command(&self) -> RawCommand {
455        let command = match self.0 {
456            Song::Position(pos) => RawCommand::new("seek").argument(pos),
457            Song::Id(id) => RawCommand::new("seekid").argument(id),
458        };
459
460        command.argument(self.1)
461    }
462
463    fn response(self, _: Frame) -> Result<Self::Response, TypedResponseError> {
464        Ok(())
465    }
466}
467
468/// `seekcur` command.
469///
470/// Seek in the current song.
471#[derive(Clone, Copy, Debug, PartialEq, Eq)]
472pub struct Seek(pub SeekMode);
473
474impl Command for Seek {
475    type Response = ();
476
477    fn command(&self) -> RawCommand {
478        let time = match self.0 {
479            SeekMode::Absolute(pos) => format!("{:.3}", pos.as_secs_f64()),
480            SeekMode::Forward(time) => format!("+{:.3}", time.as_secs_f64()),
481            SeekMode::Backward(time) => format!("-{:.3}", time.as_secs_f64()),
482        };
483
484        RawCommand::new("seekcur").argument(time)
485    }
486
487    fn response(self, _: Frame) -> Result<Self::Response, TypedResponseError> {
488        Ok(())
489    }
490}
491
492/// `shuffle` command
493#[derive(Clone, Debug, PartialEq, Eq)]
494pub struct Shuffle(Option<SongRange>);
495
496impl Shuffle {
497    /// Shuffle entire queue
498    pub fn all() -> Self {
499        Self(None)
500    }
501
502    /// Shuffle a range of songs
503    ///
504    /// The range must have at least a lower bound.
505    pub fn range<R>(range: R) -> Self
506    where
507        R: RangeBounds<SongPosition>,
508    {
509        Self(Some(SongRange::new(range)))
510    }
511}
512
513impl Command for Shuffle {
514    type Response = ();
515
516    fn command(&self) -> RawCommand {
517        match self.0 {
518            None => RawCommand::new("shuffle"),
519            Some(range) => RawCommand::new("shuffle").argument(range),
520        }
521    }
522
523    fn response(self, _: Frame) -> Result<Self::Response, TypedResponseError> {
524        Ok(())
525    }
526}
527
528/// `play` and `playid` commands.
529#[derive(Clone, Debug, PartialEq, Eq)]
530pub struct Play(Option<Song>);
531
532impl Play {
533    /// Play the current song (when paused or stopped).
534    pub fn current() -> Self {
535        Self(None)
536    }
537
538    /// Play the given song.
539    pub fn song<S>(song: S) -> Self
540    where
541        S: Into<Song>,
542    {
543        Self(Some(song.into()))
544    }
545}
546
547impl Command for Play {
548    type Response = ();
549
550    fn command(&self) -> RawCommand {
551        match self.0 {
552            None => RawCommand::new("play"),
553            Some(Song::Position(pos)) => RawCommand::new("play").argument(pos),
554            Some(Song::Id(id)) => RawCommand::new("playid").argument(id),
555        }
556    }
557
558    fn response(self, _: Frame) -> Result<Self::Response, TypedResponseError> {
559        Ok(())
560    }
561}
562
563#[derive(Clone, Copy, Debug, PartialEq, Eq)]
564enum PositionOrRelative {
565    Absolute(SongPosition),
566    BeforeCurrent(usize),
567    AfterCurrent(usize),
568}
569
570impl Argument for PositionOrRelative {
571    fn render(&self, buf: &mut BytesMut) {
572        match self {
573            PositionOrRelative::Absolute(pos) => pos.render(buf),
574            PositionOrRelative::AfterCurrent(x) => write!(buf, "+{x}").unwrap(),
575            PositionOrRelative::BeforeCurrent(x) => write!(buf, "-{x}").unwrap(),
576        }
577    }
578}
579
580/// `addid` command.
581///
582/// Add a song to the queue, returning its ID. If neither of [`Add::at`], [`Add::before_current`],
583/// or [`Add::after_current`] is used, the song will be appended to the queue.
584#[derive(Clone, Debug, PartialEq, Eq)]
585pub struct Add<'a> {
586    uri: &'a str,
587    position: Option<PositionOrRelative>,
588}
589
590impl<'a> Add<'a> {
591    /// Add the song with the given URI.
592    ///
593    /// Only individual files are supported.
594    pub fn uri(uri: &'a str) -> Self {
595        Self {
596            uri,
597            position: None,
598        }
599    }
600
601    /// Add the URI at the given position in the queue.
602    pub fn at<P: Into<SongPosition>>(mut self, position: P) -> Self {
603        self.position = Some(PositionOrRelative::Absolute(position.into()));
604        self
605    }
606
607    /// Add the URI `delta` positions before the current song.
608    ///
609    /// A `delta` of 0 is immediately before the current song.
610    ///
611    /// **NOTE**: Supported on protocol versions later than 0.23.
612    pub fn before_current(mut self, delta: usize) -> Self {
613        self.position = Some(PositionOrRelative::BeforeCurrent(delta));
614        self
615    }
616
617    /// Add the URI `delta` positions after the current song.
618    ///
619    /// A `delta` of 0 is immediately after the current song.
620    ///
621    /// **NOTE**: Supported on protocol versions later than 0.23.
622    pub fn after_current(mut self, delta: usize) -> Self {
623        self.position = Some(PositionOrRelative::AfterCurrent(delta));
624        self
625    }
626}
627
628impl<'a> Command for Add<'a> {
629    type Response = SongId;
630
631    fn command(&self) -> RawCommand {
632        let mut command = RawCommand::new("addid").argument(self.uri);
633
634        if let Some(pos) = self.position {
635            command.add_argument(pos).unwrap();
636        }
637
638        command
639    }
640
641    fn response(self, mut frame: Frame) -> Result<Self::Response, TypedResponseError> {
642        value(&mut frame, "Id").map(SongId)
643    }
644}
645
646/// `delete` and `deleteid` commands.
647#[derive(Clone, Debug, PartialEq, Eq)]
648pub struct Delete(Target);
649
650#[derive(Clone, Copy, Debug, PartialEq, Eq)]
651enum Target {
652    Id(SongId),
653    Range(SongRange),
654}
655
656impl Delete {
657    /// Remove the given ID from the queue.
658    pub fn id(id: SongId) -> Self {
659        Self(Target::Id(id))
660    }
661
662    /// Remove the song at the given position from the queue.
663    pub fn position(pos: SongPosition) -> Self {
664        let range = SongRange::new(pos..=pos);
665        Self(Target::Range(range))
666    }
667
668    /// Remove the given range from the queue.
669    ///
670    /// The range must have at least a lower bound.
671    pub fn range<R>(range: R) -> Self
672    where
673        R: RangeBounds<SongPosition>,
674    {
675        Self(Target::Range(SongRange::new(range)))
676    }
677}
678
679impl Command for Delete {
680    type Response = ();
681
682    fn command(&self) -> RawCommand {
683        match self.0 {
684            Target::Id(id) => RawCommand::new("deleteid").argument(id),
685            Target::Range(range) => RawCommand::new("delete").argument(range),
686        }
687    }
688
689    fn response(self, _: Frame) -> Result<Self::Response, TypedResponseError> {
690        Ok(())
691    }
692}
693
694/// `move` and `moveid` commands.
695#[derive(Clone, Debug, PartialEq, Eq)]
696pub struct Move {
697    from: Target,
698    to: PositionOrRelative,
699}
700
701impl Move {
702    /// Move the song with the given ID.
703    pub fn id(id: SongId) -> MoveBuilder {
704        MoveBuilder(Target::Id(id))
705    }
706
707    /// Move the song at the given position.
708    pub fn position(position: SongPosition) -> MoveBuilder {
709        MoveBuilder(Target::Range(SongRange::new(position..=position)))
710    }
711
712    /// Move the given range of song positions.
713    ///
714    /// # Panics
715    ///
716    /// The given range must have an end. If a range with an open end is passed, this will panic.
717    pub fn range<R>(range: R) -> MoveBuilder
718    where
719        R: RangeBounds<SongPosition>,
720    {
721        if let Bound::Unbounded = range.end_bound() {
722            panic!("move commands must not have an open end");
723        }
724
725        MoveBuilder(Target::Range(SongRange::new(range)))
726    }
727}
728
729/// Builder for `move` or `moveid` commands.
730///
731/// Returned by methods on [`Move`].
732#[must_use]
733#[derive(Clone, Debug, PartialEq, Eq)]
734pub struct MoveBuilder(Target);
735
736impl MoveBuilder {
737    /// Move the selection to the given absolute queue position.
738    pub fn to_position(self, position: SongPosition) -> Move {
739        Move {
740            from: self.0,
741            to: PositionOrRelative::Absolute(position),
742        }
743    }
744
745    /// Move the selection to the given `delta` after the current song.
746    ///
747    /// A `delta` of 0 means immediately after the current song.
748    pub fn after_current(self, delta: usize) -> Move {
749        Move {
750            from: self.0,
751            to: PositionOrRelative::AfterCurrent(delta),
752        }
753    }
754
755    /// Move the selection to the given `delta` before the current song.
756    ///
757    /// A `delta` of 0 means immediately before the current song.
758    pub fn before_current(self, delta: usize) -> Move {
759        Move {
760            from: self.0,
761            to: PositionOrRelative::BeforeCurrent(delta),
762        }
763    }
764}
765
766impl Command for Move {
767    type Response = ();
768
769    fn command(&self) -> RawCommand {
770        let command = match self.from {
771            Target::Id(id) => RawCommand::new("moveid").argument(id),
772            Target::Range(range) => RawCommand::new("move").argument(range),
773        };
774
775        command.argument(self.to)
776    }
777
778    fn response(self, _: Frame) -> Result<Self::Response, TypedResponseError> {
779        Ok(())
780    }
781}
782
783/// `find` command.
784#[derive(Clone, Debug, PartialEq, Eq)]
785pub struct Find {
786    filter: Filter,
787    sort: Option<Tag>,
788    window: Option<SongRange>,
789}
790
791impl Find {
792    /// Find all songs matching `filter`.
793    pub fn new(filter: Filter) -> Self {
794        Self {
795            filter,
796            sort: None,
797            window: None,
798        }
799    }
800
801    /// Sort the result by the given tag.
802    ///
803    /// This does some special-casing for certain tags, see the [MPD documentation][0] for details.
804    ///
805    /// # Panics
806    ///
807    /// This will panic when sending the command if you pass a malformed value using the
808    /// [`Other`][error] variant.
809    ///
810    /// [0]: https://www.musicpd.org/doc/html/protocol.html#command-find
811    /// [error]: crate::tag::Tag::Other
812    pub fn sort(mut self, sort_by: Tag) -> Self {
813        self.sort = Some(sort_by);
814        self
815    }
816
817    /// Limit the result to the given window.
818    pub fn window<R>(mut self, window: R) -> Self
819    where
820        R: RangeBounds<usize>,
821    {
822        self.window = Some(SongRange::new_usize(window));
823        self
824    }
825}
826
827impl Command for Find {
828    type Response = Vec<res::Song>;
829
830    fn command(&self) -> RawCommand {
831        let mut command = RawCommand::new("find").argument(&self.filter);
832
833        if let Some(sort) = &self.sort {
834            command.add_argument("sort").unwrap();
835            command
836                .add_argument(sort.as_str())
837                .expect("Invalid sort value");
838        }
839
840        if let Some(window) = self.window {
841            command.add_argument("window").unwrap();
842            command.add_argument(window).unwrap();
843        }
844
845        command
846    }
847
848    fn response(self, frame: Frame) -> Result<Self::Response, TypedResponseError> {
849        res::Song::from_frame_multi(frame)
850    }
851}
852
853/// `list` command.
854#[derive(Clone, Debug, PartialEq, Eq)]
855pub struct List<const N: usize = 0> {
856    tag: Tag,
857    filter: Option<Filter>,
858    group_by: [Tag; N],
859}
860
861impl List<0> {
862    /// List distinct values of `tag`.
863    pub fn new(tag: Tag) -> List<0> {
864        List {
865            tag,
866            filter: None,
867            group_by: [],
868        }
869    }
870}
871
872impl<const N: usize> List<N> {
873    /// Filter the songs being considered using the given `filter`.
874    ///
875    /// This will overwrite the filter if called multiple times.
876    pub fn filter(mut self, filter: Filter) -> Self {
877        self.filter = Some(filter);
878        self
879    }
880
881    /// Group results by the given tag.
882    ///
883    /// This will overwrite the grouping if called multiple times.
884    pub fn group_by<const M: usize>(self, group_by: [Tag; M]) -> List<M> {
885        List {
886            tag: self.tag,
887            filter: self.filter,
888            group_by,
889        }
890    }
891}
892
893impl<const N: usize> Command for List<N> {
894    type Response = res::List<N>;
895
896    fn command(&self) -> RawCommand {
897        let mut command = RawCommand::new("list").argument(&self.tag);
898
899        if let Some(filter) = self.filter.as_ref() {
900            command.add_argument(filter).unwrap();
901        }
902
903        for group_by in &self.group_by {
904            command.add_argument("group").unwrap();
905            command.add_argument(group_by).unwrap();
906        }
907
908        command
909    }
910
911    fn response(self, frame: Frame) -> Result<Self::Response, TypedResponseError> {
912        Ok(res::List::from_frame(self.tag, self.group_by, frame))
913    }
914}
915
916/// `count` command without grouping.
917#[derive(Clone, Debug, PartialEq, Eq)]
918pub struct Count {
919    filter: Filter,
920}
921
922impl Count {
923    /// Count the number and total playtime of all songs matching the given filter.
924    pub fn new(filter: Filter) -> Count {
925        Count { filter }
926    }
927
928    /// Group the results by the given tag.
929    pub fn group_by(self, group_by: Tag) -> CountGrouped {
930        CountGrouped {
931            filter: Some(self.filter),
932            group_by,
933        }
934    }
935}
936
937impl Command for Count {
938    type Response = res::Count;
939
940    fn command(&self) -> RawCommand {
941        RawCommand::new("count").argument(&self.filter)
942    }
943
944    fn response(self, frame: Frame) -> Result<Self::Response, TypedResponseError> {
945        res::Count::from_frame(frame)
946    }
947}
948
949/// `count` command with grouping.
950#[derive(Clone, Debug, PartialEq, Eq)]
951pub struct CountGrouped {
952    group_by: Tag,
953    filter: Option<Filter>,
954}
955
956impl CountGrouped {
957    /// Count the number and total playtime of songs grouped by the given tag.
958    pub fn new(group_by: Tag) -> CountGrouped {
959        CountGrouped {
960            group_by,
961            filter: None,
962        }
963    }
964
965    /// Only consider songs matching the given filter.
966    ///
967    /// If called multiple times, this will overwrite the filter.
968    pub fn filter(mut self, filter: Filter) -> CountGrouped {
969        self.filter = Some(filter);
970        self
971    }
972}
973
974impl Command for CountGrouped {
975    type Response = Vec<(String, res::Count)>;
976
977    fn command(&self) -> RawCommand {
978        let mut cmd = RawCommand::new("count");
979
980        if let Some(filter) = &self.filter {
981            cmd.add_argument(filter).unwrap();
982        }
983
984        cmd.argument("group").argument(&self.group_by)
985    }
986
987    fn response(self, frame: Frame) -> Result<Self::Response, TypedResponseError> {
988        res::Count::from_frame_grouped(frame, &self.group_by)
989    }
990}
991
992/// `rename` command.
993#[derive(Clone, Debug, PartialEq, Eq)]
994pub struct RenamePlaylist<'a> {
995    from: &'a str,
996    to: &'a str,
997}
998
999impl<'a> RenamePlaylist<'a> {
1000    /// Rename the playlist named `from` to `to`.
1001    pub fn new(from: &'a str, to: &'a str) -> Self {
1002        Self { from, to }
1003    }
1004}
1005
1006impl<'a> Command for RenamePlaylist<'a> {
1007    type Response = ();
1008
1009    fn command(&self) -> RawCommand {
1010        RawCommand::new("rename")
1011            .argument(self.from)
1012            .argument(self.to)
1013    }
1014
1015    fn response(self, _: Frame) -> Result<Self::Response, TypedResponseError> {
1016        Ok(())
1017    }
1018}
1019
1020/// `load` command.
1021#[derive(Clone, Debug, PartialEq, Eq)]
1022pub struct LoadPlaylist<'a> {
1023    name: &'a str,
1024    range: Option<SongRange>,
1025}
1026
1027impl<'a> LoadPlaylist<'a> {
1028    /// Load the playlist with the given name into the queue.
1029    pub fn name(name: &'a str) -> Self {
1030        Self { name, range: None }
1031    }
1032
1033    /// Limit the loaded playlist to the given window.
1034    pub fn range<R>(mut self, range: R) -> Self
1035    where
1036        R: RangeBounds<usize>,
1037    {
1038        self.range = Some(SongRange::new_usize(range));
1039        self
1040    }
1041}
1042
1043impl<'a> Command for LoadPlaylist<'a> {
1044    type Response = ();
1045
1046    fn command(&self) -> RawCommand {
1047        let mut command = RawCommand::new("load").argument(self.name);
1048
1049        if let Some(range) = self.range {
1050            command.add_argument(range).unwrap();
1051        }
1052
1053        command
1054    }
1055
1056    fn response(self, _: Frame) -> Result<Self::Response, TypedResponseError> {
1057        Ok(())
1058    }
1059}
1060
1061/// `playlistadd` command.
1062///
1063/// If [`AddToPlaylist::at`] is not used, the song will be appended to the playlist.
1064#[derive(Clone, Debug, PartialEq, Eq)]
1065pub struct AddToPlaylist<'a> {
1066    playlist: &'a str,
1067    song_url: &'a str,
1068    position: Option<SongPosition>,
1069}
1070
1071impl<'a> AddToPlaylist<'a> {
1072    /// Add `song_url` to `playlist`.
1073    pub fn new(playlist: &'a str, song_url: &'a str) -> Self {
1074        Self {
1075            playlist,
1076            song_url,
1077            position: None,
1078        }
1079    }
1080
1081    /// Add the URI at the given position in the queue.
1082    ///
1083    /// **NOTE**: Supported on protocol versions later than 0.23.3.
1084    pub fn at<P: Into<SongPosition>>(mut self, position: P) -> Self {
1085        self.position = Some(position.into());
1086        self
1087    }
1088}
1089
1090impl<'a> Command for AddToPlaylist<'a> {
1091    type Response = ();
1092
1093    fn command(&self) -> RawCommand {
1094        let mut command = RawCommand::new("playlistadd")
1095            .argument(self.playlist)
1096            .argument(self.song_url);
1097
1098        if let Some(pos) = self.position {
1099            command.add_argument(pos).unwrap();
1100        }
1101
1102        command
1103    }
1104
1105    fn response(self, _: Frame) -> Result<Self::Response, TypedResponseError> {
1106        Ok(())
1107    }
1108}
1109
1110/// `playlistdelete` command.
1111#[derive(Clone, Debug, PartialEq, Eq)]
1112pub struct RemoveFromPlaylist<'a> {
1113    playlist: &'a str,
1114    target: PositionOrRange,
1115}
1116
1117#[derive(Clone, Debug, PartialEq, Eq)]
1118enum PositionOrRange {
1119    Position(usize),
1120    Range(SongRange),
1121}
1122
1123impl<'a> RemoveFromPlaylist<'a> {
1124    /// Delete the song at `position` from `playlist`.
1125    pub fn position(playlist: &'a str, position: usize) -> Self {
1126        RemoveFromPlaylist {
1127            playlist,
1128            target: PositionOrRange::Position(position),
1129        }
1130    }
1131
1132    /// Delete the specified range of songs from `playlist`.
1133    pub fn range<R>(playlist: &'a str, range: R) -> Self
1134    where
1135        R: RangeBounds<SongPosition>,
1136    {
1137        RemoveFromPlaylist {
1138            playlist,
1139            target: PositionOrRange::Range(SongRange::new(range)),
1140        }
1141    }
1142}
1143
1144impl<'a> Command for RemoveFromPlaylist<'a> {
1145    type Response = ();
1146
1147    fn command(&self) -> RawCommand {
1148        let command = RawCommand::new("playlistdelete").argument(self.playlist);
1149
1150        match self.target {
1151            PositionOrRange::Position(p) => command.argument(p),
1152            PositionOrRange::Range(r) => command.argument(r),
1153        }
1154    }
1155
1156    fn response(self, _: Frame) -> Result<Self::Response, TypedResponseError> {
1157        Ok(())
1158    }
1159}
1160
1161/// `playlistmove` command.
1162#[derive(Clone, Debug, PartialEq, Eq)]
1163pub struct MoveInPlaylist<'a> {
1164    playlist: &'a str,
1165    from: usize,
1166    to: usize,
1167}
1168
1169impl<'a> MoveInPlaylist<'a> {
1170    /// Move the song at `from` to `to` in the playlist named `playlist`.
1171    pub fn new(playlist: &'a str, from: usize, to: usize) -> Self {
1172        Self { playlist, from, to }
1173    }
1174}
1175
1176impl<'a> Command for MoveInPlaylist<'a> {
1177    type Response = ();
1178
1179    fn command(&self) -> RawCommand {
1180        RawCommand::new("playlistmove")
1181            .argument(self.playlist)
1182            .argument(self.from)
1183            .argument(self.to)
1184    }
1185
1186    fn response(self, _: Frame) -> Result<Self::Response, TypedResponseError> {
1187        Ok(())
1188    }
1189}
1190
1191/// `listallinfo` command.
1192#[derive(Clone, Debug, PartialEq, Eq)]
1193pub struct ListAllIn<'a> {
1194    directory: &'a str,
1195}
1196
1197impl<'a> ListAllIn<'a> {
1198    /// List all songs in the library.
1199    pub fn root() -> ListAllIn<'static> {
1200        ListAllIn { directory: "" }
1201    }
1202
1203    /// List all songs beneath the given directory.
1204    pub fn directory(directory: &'a str) -> Self {
1205        Self { directory }
1206    }
1207}
1208
1209impl<'a> Command for ListAllIn<'a> {
1210    type Response = Vec<res::Song>;
1211
1212    fn command(&self) -> RawCommand {
1213        let mut command = RawCommand::new("listallinfo");
1214
1215        if !self.directory.is_empty() {
1216            command.add_argument(self.directory).unwrap();
1217        }
1218
1219        command
1220    }
1221
1222    fn response(self, frame: Frame) -> Result<Self::Response, TypedResponseError> {
1223        res::Song::from_frame_multi(frame)
1224    }
1225}
1226
1227/// Set the response binary length limit, in bytes.
1228///
1229/// This can dramatically speed up operations like [loading album art][crate::Client::album_art],
1230/// but may cause undesirable latency when using MPD over a slow connection.
1231#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1232pub struct SetBinaryLimit(pub usize);
1233
1234impl Command for SetBinaryLimit {
1235    type Response = ();
1236
1237    fn command(&self) -> RawCommand {
1238        RawCommand::new("binarylimit").argument(self.0)
1239    }
1240
1241    fn response(self, _: Frame) -> Result<Self::Response, TypedResponseError> {
1242        Ok(())
1243    }
1244}
1245
1246/// `albumart` command.
1247#[derive(Clone, Debug, PartialEq, Eq)]
1248pub struct AlbumArt<'a> {
1249    uri: &'a str,
1250    offset: usize,
1251}
1252
1253impl<'a> AlbumArt<'a> {
1254    /// Get the separate file album art for the given URI.
1255    pub fn new(uri: &'a str) -> Self {
1256        Self { uri, offset: 0 }
1257    }
1258
1259    /// Load the resulting data starting from the given offset.
1260    pub fn offset(self, offset: usize) -> Self {
1261        Self { offset, ..self }
1262    }
1263}
1264
1265impl<'a> Command for AlbumArt<'a> {
1266    type Response = Option<res::AlbumArt>;
1267
1268    fn command(&self) -> RawCommand {
1269        RawCommand::new("albumart")
1270            .argument(self.uri)
1271            .argument(self.offset)
1272    }
1273
1274    fn response(self, frame: Frame) -> Result<Self::Response, TypedResponseError> {
1275        res::AlbumArt::from_frame(frame)
1276    }
1277}
1278
1279/// `readpicture` command.
1280#[derive(Clone, Debug, PartialEq, Eq)]
1281pub struct AlbumArtEmbedded<'a> {
1282    uri: &'a str,
1283    offset: usize,
1284}
1285
1286impl<'a> AlbumArtEmbedded<'a> {
1287    /// Get the separate file album art for the given URI.
1288    pub fn new(uri: &'a str) -> Self {
1289        Self { uri, offset: 0 }
1290    }
1291
1292    /// Load the resulting data starting from the given offset.
1293    pub fn offset(self, offset: usize) -> Self {
1294        Self { offset, ..self }
1295    }
1296}
1297
1298impl<'a> Command for AlbumArtEmbedded<'a> {
1299    type Response = Option<res::AlbumArt>;
1300
1301    fn command(&self) -> RawCommand {
1302        RawCommand::new("readpicture")
1303            .argument(self.uri)
1304            .argument(self.offset)
1305    }
1306
1307    fn response(self, frame: Frame) -> Result<Self::Response, TypedResponseError> {
1308        res::AlbumArt::from_frame(frame)
1309    }
1310}
1311
1312/// Manage enabled tag types.
1313#[derive(Clone, Debug, PartialEq, Eq)]
1314pub struct TagTypes<'a>(TagTypesAction<'a>);
1315
1316impl<'a> TagTypes<'a> {
1317    /// Enable all tags.
1318    pub fn enable_all() -> TagTypes<'static> {
1319        TagTypes(TagTypesAction::EnableAll)
1320    }
1321
1322    /// Disable all tags.
1323    pub fn disable_all() -> TagTypes<'static> {
1324        TagTypes(TagTypesAction::Clear)
1325    }
1326
1327    /// Disable the given list of tags.
1328    ///
1329    /// # Panics
1330    ///
1331    /// Panics if called with an empty list of tags.
1332    pub fn disable(tags: &'a [Tag]) -> TagTypes<'a> {
1333        assert_ne!(tags.len(), 0, "The list of tags must not be empty");
1334        TagTypes(TagTypesAction::Disable(tags))
1335    }
1336
1337    /// Enable the given list of tags.
1338    ///
1339    /// # Panics
1340    ///
1341    /// Panics if called with an empty list of tags.
1342    pub fn enable(tags: &'a [Tag]) -> TagTypes<'a> {
1343        assert_ne!(tags.len(), 0, "The list of tags must not be empty");
1344        TagTypes(TagTypesAction::Enable(tags))
1345    }
1346}
1347
1348impl<'a> Command for TagTypes<'a> {
1349    type Response = ();
1350
1351    fn command(&self) -> RawCommand {
1352        let mut cmd = RawCommand::new("tagtypes");
1353
1354        match &self.0 {
1355            TagTypesAction::EnableAll => cmd.add_argument("all").unwrap(),
1356            TagTypesAction::Clear => cmd.add_argument("clear").unwrap(),
1357            TagTypesAction::Disable(tags) => {
1358                cmd.add_argument("disable").unwrap();
1359
1360                for tag in tags.iter() {
1361                    cmd.add_argument(tag).unwrap();
1362                }
1363            }
1364            TagTypesAction::Enable(tags) => {
1365                cmd.add_argument("enable").unwrap();
1366
1367                for tag in tags.iter() {
1368                    cmd.add_argument(tag).unwrap();
1369                }
1370            }
1371        }
1372
1373        cmd
1374    }
1375
1376    fn response(self, _: Frame) -> Result<Self::Response, TypedResponseError> {
1377        Ok(())
1378    }
1379}
1380
1381#[derive(Clone, Debug, PartialEq, Eq)]
1382enum TagTypesAction<'a> {
1383    EnableAll,
1384    Clear,
1385    Disable(&'a [Tag]),
1386    Enable(&'a [Tag]),
1387}
1388
1389/// `sticker get` command
1390#[derive(Clone, Debug, PartialEq, Eq)]
1391pub struct StickerGet<'a> {
1392    uri: &'a str,
1393    name: &'a str,
1394}
1395
1396impl<'a> StickerGet<'a> {
1397    /// Get the sticker `name` for the song at `uri`
1398    pub fn new(uri: &'a str, name: &'a str) -> Self {
1399        Self { uri, name }
1400    }
1401}
1402
1403impl<'a> Command for StickerGet<'a> {
1404    type Response = res::StickerGet;
1405
1406    fn command(&self) -> RawCommand {
1407        RawCommand::new("sticker")
1408            .argument("get")
1409            .argument("song")
1410            .argument(self.uri)
1411            .argument(self.name)
1412    }
1413
1414    fn response(self, frame: Frame) -> Result<Self::Response, TypedResponseError> {
1415        res::StickerGet::from_frame(frame)
1416    }
1417}
1418
1419/// `sticker set` command
1420#[derive(Clone, Debug, PartialEq, Eq)]
1421pub struct StickerSet<'a> {
1422    uri: &'a str,
1423    name: &'a str,
1424    value: &'a str,
1425}
1426
1427impl<'a> StickerSet<'a> {
1428    /// Set the sticker `name` to `value` for the song at `uri`
1429    pub fn new(uri: &'a str, name: &'a str, value: &'a str) -> Self {
1430        Self { uri, name, value }
1431    }
1432}
1433
1434impl<'a> Command for StickerSet<'a> {
1435    type Response = ();
1436
1437    fn command(&self) -> RawCommand {
1438        RawCommand::new("sticker")
1439            .argument("set")
1440            .argument("song")
1441            .argument(self.uri)
1442            .argument(self.name)
1443            .argument(self.value)
1444    }
1445
1446    fn response(self, _: Frame) -> Result<Self::Response, TypedResponseError> {
1447        Ok(())
1448    }
1449}
1450
1451/// `sticker delete` command
1452#[derive(Clone, Debug, PartialEq, Eq)]
1453pub struct StickerDelete<'a> {
1454    uri: &'a str,
1455    name: &'a str,
1456}
1457
1458impl<'a> StickerDelete<'a> {
1459    /// Delete the sticker `name` for the song at `uri`
1460    pub fn new(uri: &'a str, name: &'a str) -> Self {
1461        Self { uri, name }
1462    }
1463}
1464
1465impl<'a> Command for StickerDelete<'a> {
1466    type Response = ();
1467
1468    fn command(&self) -> RawCommand {
1469        RawCommand::new("sticker")
1470            .argument("delete")
1471            .argument("song")
1472            .argument(self.uri)
1473            .argument(self.name)
1474    }
1475
1476    fn response(self, _: Frame) -> Result<Self::Response, TypedResponseError> {
1477        Ok(())
1478    }
1479}
1480
1481/// `sticker list` command
1482#[derive(Clone, Debug, PartialEq, Eq)]
1483pub struct StickerList<'a> {
1484    uri: &'a str,
1485}
1486
1487impl<'a> StickerList<'a> {
1488    /// Lists all stickers on the song at `uri`
1489    pub fn new(uri: &'a str) -> Self {
1490        Self { uri }
1491    }
1492}
1493
1494impl<'a> Command for StickerList<'a> {
1495    type Response = res::StickerList;
1496
1497    fn command(&self) -> RawCommand {
1498        RawCommand::new("sticker")
1499            .argument("list")
1500            .argument("song")
1501            .argument(self.uri)
1502    }
1503
1504    fn response(self, frame: Frame) -> Result<Self::Response, TypedResponseError> {
1505        res::StickerList::from_frame(frame)
1506    }
1507}
1508
1509/// Operator for full (filtered) version
1510/// of `sticker find` command
1511#[derive(Clone, Debug, PartialEq, Eq)]
1512enum StickerFindOperator {
1513    /// = operator
1514    Equals,
1515    /// < operator
1516    LessThan,
1517    /// > operator
1518    GreaterThan,
1519}
1520
1521/// `sticker find` command
1522#[derive(Clone, Debug, PartialEq, Eq)]
1523pub struct StickerFind<'a> {
1524    uri: &'a str,
1525    name: &'a str,
1526    filter: Option<(StickerFindOperator, &'a str)>,
1527}
1528
1529impl<'a> StickerFind<'a> {
1530    /// Lists all stickers on the song at `uri`
1531    pub fn new(uri: &'a str, name: &'a str) -> Self {
1532        Self {
1533            uri,
1534            name,
1535            filter: None,
1536        }
1537    }
1538
1539    /// Find stickers where their value is equal to `value`
1540    pub fn where_eq(self, value: &'a str) -> Self {
1541        self.add_filter(StickerFindOperator::Equals, value)
1542    }
1543
1544    /// Find stickers where their value is greater than `value`
1545    pub fn where_gt(self, value: &'a str) -> Self {
1546        self.add_filter(StickerFindOperator::GreaterThan, value)
1547    }
1548
1549    /// Find stickers where their value is less than `value`
1550    pub fn where_lt(self, value: &'a str) -> Self {
1551        self.add_filter(StickerFindOperator::LessThan, value)
1552    }
1553
1554    fn add_filter(self, operator: StickerFindOperator, value: &'a str) -> Self {
1555        Self {
1556            name: self.name,
1557            uri: self.uri,
1558            filter: Some((operator, value)),
1559        }
1560    }
1561}
1562
1563impl<'a> Command for StickerFind<'a> {
1564    type Response = res::StickerFind;
1565
1566    fn command(&self) -> RawCommand {
1567        let base = RawCommand::new("sticker")
1568            .argument("find")
1569            .argument("song")
1570            .argument(self.uri)
1571            .argument(self.name);
1572
1573        if let Some((operator, value)) = self.filter.as_ref() {
1574            match operator {
1575                StickerFindOperator::Equals => base.argument("=").argument(value),
1576                StickerFindOperator::GreaterThan => base.argument(">").argument(value),
1577                StickerFindOperator::LessThan => base.argument("<").argument(value),
1578            }
1579        } else {
1580            base
1581        }
1582    }
1583
1584    fn response(self, frame: Frame) -> Result<Self::Response, TypedResponseError> {
1585        res::StickerFind::from_frame(frame)
1586    }
1587}
1588
1589/// `update` command.
1590#[derive(Clone, Debug, PartialEq, Eq)]
1591pub struct Update<'a>(Option<&'a str>);
1592
1593impl<'a> Update<'a> {
1594    /// Update the entire music database.
1595    pub fn new() -> Self {
1596        Update(None)
1597    }
1598
1599    /// Restrict the update to the files below the given path.
1600    pub fn uri(self, uri: &'a str) -> Self {
1601        Self(Some(uri))
1602    }
1603}
1604
1605impl<'a> Command for Update<'a> {
1606    type Response = u64;
1607
1608    fn command(&self) -> RawCommand {
1609        let mut command = RawCommand::new("update");
1610
1611        if let Some(uri) = self.0 {
1612            command.add_argument(uri).unwrap();
1613        }
1614
1615        command
1616    }
1617
1618    fn response(self, mut frame: Frame) -> Result<Self::Response, TypedResponseError> {
1619        value(&mut frame, "updating_db")
1620    }
1621}
1622
1623impl<'a> Default for Update<'a> {
1624    fn default() -> Self {
1625        Update::new()
1626    }
1627}
1628
1629/// `rescan` command.
1630///
1631/// Unlike the [`Update`] command, this will also scan files that don't appear to have changed
1632/// based on their modification time.
1633#[derive(Clone, Debug, PartialEq, Eq)]
1634pub struct Rescan<'a>(Option<&'a str>);
1635
1636impl<'a> Rescan<'a> {
1637    /// Rescan the entire music database.
1638    pub fn new() -> Self {
1639        Rescan(None)
1640    }
1641
1642    /// Restrict the rescan to the files below the given path.
1643    pub fn uri(self, uri: &'a str) -> Self {
1644        Self(Some(uri))
1645    }
1646}
1647
1648impl<'a> Command for Rescan<'a> {
1649    type Response = u64;
1650
1651    fn command(&self) -> RawCommand {
1652        let mut command = RawCommand::new("rescan");
1653
1654        if let Some(uri) = self.0 {
1655            command.add_argument(uri).unwrap();
1656        }
1657
1658        command
1659    }
1660
1661    fn response(self, mut frame: Frame) -> Result<Self::Response, TypedResponseError> {
1662        value(&mut frame, "updating_db")
1663    }
1664}
1665
1666impl<'a> Default for Rescan<'a> {
1667    fn default() -> Self {
1668        Rescan::new()
1669    }
1670}
1671
1672/// `readmessage` command.
1673#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1674pub struct ReadChannelMessages;
1675
1676impl Command for ReadChannelMessages {
1677    type Response = Vec<(String, String)>;
1678
1679    fn command(&self) -> RawCommand {
1680        RawCommand::new("readmessages")
1681    }
1682
1683    fn response(self, frame: Frame) -> Result<Self::Response, TypedResponseError> {
1684        res::parse_channel_messages(frame)
1685    }
1686}
1687
1688/// `channels` command.
1689#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1690pub struct ListChannels;
1691
1692impl Command for ListChannels {
1693    type Response = Vec<String>;
1694
1695    fn command(&self) -> RawCommand {
1696        RawCommand::new("channels")
1697    }
1698
1699    fn response(self, frame: Frame) -> Result<Self::Response, TypedResponseError> {
1700        let mut response = Vec::with_capacity(frame.fields_len());
1701        for (key, value) in frame {
1702            if &*key != "channel" {
1703                return Err(TypedResponseError::unexpected_field("channel", &*key));
1704            }
1705
1706            response.push(value);
1707        }
1708
1709        Ok(response)
1710    }
1711}
1712
1713/// `sendmessage` command.
1714#[derive(Debug, Clone, PartialEq, Eq)]
1715pub struct SendChannelMessage<'a> {
1716    channel: &'a str,
1717    message: &'a str,
1718}
1719
1720impl<'a> SendChannelMessage<'a> {
1721    /// Send the given message to the given channel.
1722    pub fn new(channel: &'a str, message: &'a str) -> SendChannelMessage<'a> {
1723        SendChannelMessage { channel, message }
1724    }
1725}
1726
1727impl<'a> Command for SendChannelMessage<'a> {
1728    type Response = ();
1729
1730    fn command(&self) -> RawCommand {
1731        RawCommand::new("sendmessage")
1732            .argument(self.channel)
1733            .argument(self.message)
1734    }
1735
1736    fn response(self, _frame: Frame) -> Result<Self::Response, TypedResponseError> {
1737        Ok(())
1738    }
1739}
1740
1741#[cfg(test)]
1742mod tests {
1743    use super::*;
1744
1745    #[test]
1746    fn range_arg() {
1747        let mut buf = BytesMut::new();
1748
1749        SongRange::new_usize(2..4).render(&mut buf);
1750        assert_eq!(buf, "2:4");
1751        buf.clear();
1752
1753        SongRange::new_usize(3..).render(&mut buf);
1754        assert_eq!(buf, "3:");
1755        buf.clear();
1756
1757        SongRange::new_usize(2..=5).render(&mut buf);
1758        assert_eq!(buf, "2:6");
1759        buf.clear();
1760
1761        SongRange::new_usize(..5).render(&mut buf);
1762        assert_eq!(buf, "0:5");
1763        buf.clear();
1764
1765        SongRange::new_usize(..).render(&mut buf);
1766        assert_eq!(buf, "0:");
1767        buf.clear();
1768
1769        SongRange::new_usize(1..=1).render(&mut buf);
1770        assert_eq!(buf, "1:2");
1771        buf.clear();
1772
1773        SongRange::new_usize(..usize::MAX).render(&mut buf);
1774        assert_eq!(buf, format!("0:{}", usize::MAX));
1775        buf.clear();
1776
1777        SongRange::new_usize(..=usize::MAX).render(&mut buf);
1778        assert_eq!(buf, format!("0:{}", usize::MAX));
1779        buf.clear();
1780    }
1781
1782    #[test]
1783    fn command_queue() {
1784        assert_eq!(Queue.command(), RawCommand::new("playlistinfo"));
1785        assert_eq!(
1786            Queue::song(Song::Position(SongPosition(1))).command(),
1787            RawCommand::new("playlistinfo").argument("1")
1788        );
1789        assert_eq!(
1790            Queue::song(Song::Id(SongId(7))).command(),
1791            RawCommand::new("playlistid").argument("7")
1792        );
1793        assert_eq!(
1794            Queue::range(SongPosition(3)..SongPosition(18)).command(),
1795            RawCommand::new("playlistinfo").argument("3:18")
1796        );
1797    }
1798
1799    #[test]
1800    fn command_crossfade() {
1801        assert_eq!(
1802            Crossfade(Duration::from_secs_f64(2.345)).command(),
1803            RawCommand::new("crossfade").argument("2")
1804        );
1805    }
1806
1807    #[test]
1808    fn command_getplaylist() {
1809        assert_eq!(
1810            GetPlaylist("foo").command(),
1811            RawCommand::new("listplaylistinfo").argument("foo")
1812        );
1813    }
1814
1815    #[test]
1816    fn command_volume() {
1817        assert_eq!(
1818            SetVolume(150).command(),
1819            RawCommand::new("setvol").argument("100")
1820        );
1821    }
1822
1823    #[test]
1824    fn command_seek_to() {
1825        let duration = Duration::from_secs(2);
1826
1827        assert_eq!(
1828            SeekTo(SongId(2).into(), duration).command(),
1829            RawCommand::new("seekid")
1830                .argument(SongId(2))
1831                .argument(duration)
1832        );
1833
1834        assert_eq!(
1835            SeekTo(SongPosition(2).into(), duration).command(),
1836            RawCommand::new("seek")
1837                .argument(SongPosition(2))
1838                .argument(duration)
1839        );
1840    }
1841
1842    #[test]
1843    fn command_seek() {
1844        let duration = Duration::from_secs(1);
1845
1846        assert_eq!(
1847            Seek(SeekMode::Absolute(duration)).command(),
1848            RawCommand::new("seekcur").argument("1.000")
1849        );
1850        assert_eq!(
1851            Seek(SeekMode::Forward(duration)).command(),
1852            RawCommand::new("seekcur").argument("+1.000")
1853        );
1854        assert_eq!(
1855            Seek(SeekMode::Backward(duration)).command(),
1856            RawCommand::new("seekcur").argument("-1.000")
1857        );
1858    }
1859
1860    #[test]
1861    fn command_shuffle() {
1862        assert_eq!(Shuffle::all().command(), RawCommand::new("shuffle"));
1863        assert_eq!(
1864            Shuffle::range(SongPosition(0)..SongPosition(2)).command(),
1865            RawCommand::new("shuffle").argument("0:2")
1866        );
1867    }
1868
1869    #[test]
1870    fn command_play() {
1871        assert_eq!(Play::current().command(), RawCommand::new("play"));
1872        assert_eq!(
1873            Play::song(SongPosition(2)).command(),
1874            RawCommand::new("play").argument(SongPosition(2))
1875        );
1876        assert_eq!(
1877            Play::song(SongId(2)).command(),
1878            RawCommand::new("playid").argument(SongId(2))
1879        );
1880    }
1881
1882    #[test]
1883    fn command_add() {
1884        let uri = "foo/bar.mp3";
1885
1886        assert_eq!(
1887            Add::uri(uri).command(),
1888            RawCommand::new("addid").argument(uri)
1889        );
1890        assert_eq!(
1891            Add::uri(uri).at(5).command(),
1892            RawCommand::new("addid").argument(uri).argument("5")
1893        );
1894        assert_eq!(
1895            Add::uri(uri).before_current(5).command(),
1896            RawCommand::new("addid").argument(uri).argument("-5")
1897        );
1898        assert_eq!(
1899            Add::uri(uri).after_current(5).command(),
1900            RawCommand::new("addid").argument(uri).argument("+5")
1901        );
1902    }
1903
1904    #[test]
1905    fn command_delete() {
1906        assert_eq!(
1907            Delete::id(SongId(2)).command(),
1908            RawCommand::new("deleteid").argument(SongId(2))
1909        );
1910
1911        assert_eq!(
1912            Delete::position(SongPosition(2)).command(),
1913            RawCommand::new("delete").argument("2:3")
1914        );
1915
1916        assert_eq!(
1917            Delete::range(SongPosition(2)..SongPosition(4)).command(),
1918            RawCommand::new("delete").argument("2:4")
1919        );
1920    }
1921
1922    #[test]
1923    fn command_move() {
1924        assert_eq!(
1925            Move::position(SongPosition(2))
1926                .to_position(SongPosition(4))
1927                .command(),
1928            RawCommand::new("move").argument("2:3").argument("4")
1929        );
1930
1931        assert_eq!(
1932            Move::id(SongId(2)).to_position(SongPosition(4)).command(),
1933            RawCommand::new("moveid")
1934                .argument(SongId(2))
1935                .argument(SongPosition(4))
1936        );
1937
1938        assert_eq!(
1939            Move::range(SongPosition(3)..SongPosition(5))
1940                .to_position(SongPosition(4))
1941                .command(),
1942            RawCommand::new("move")
1943                .argument("3:5")
1944                .argument(SongPosition(4))
1945        );
1946
1947        assert_eq!(
1948            Move::position(SongPosition(2)).after_current(3).command(),
1949            RawCommand::new("move").argument("2:3").argument("+3")
1950        );
1951
1952        assert_eq!(
1953            Move::position(SongPosition(2)).before_current(3).command(),
1954            RawCommand::new("move").argument("2:3").argument("-3")
1955        );
1956    }
1957
1958    #[test]
1959    fn command_find() {
1960        let filter = Filter::tag(Tag::Artist, "Foo");
1961
1962        assert_eq!(
1963            Find::new(filter.clone()).command(),
1964            RawCommand::new("find").argument(filter.clone())
1965        );
1966
1967        assert_eq!(
1968            Find::new(filter.clone()).window(..3).command(),
1969            RawCommand::new("find")
1970                .argument(filter.clone())
1971                .argument("window")
1972                .argument("0:3"),
1973        );
1974
1975        assert_eq!(
1976            Find::new(filter.clone())
1977                .window(3..)
1978                .sort(Tag::Artist)
1979                .command(),
1980            RawCommand::new("find")
1981                .argument(filter)
1982                .argument("sort")
1983                .argument("Artist")
1984                .argument("window")
1985                .argument("3:")
1986        );
1987    }
1988
1989    #[test]
1990    fn command_list() {
1991        assert_eq!(
1992            List::new(Tag::Album).command(),
1993            RawCommand::new("list").argument("Album")
1994        );
1995
1996        let filter = Filter::tag(Tag::Artist, "Foo");
1997        assert_eq!(
1998            List::new(Tag::Album).filter(filter.clone()).command(),
1999            RawCommand::new("list").argument("Album").argument(filter)
2000        );
2001
2002        let filter = Filter::tag(Tag::Artist, "Foo");
2003        assert_eq!(
2004            List::new(Tag::Title)
2005                .filter(filter.clone())
2006                .group_by([Tag::AlbumArtist, Tag::Album])
2007                .command(),
2008            RawCommand::new("list")
2009                .argument("Title")
2010                .argument(filter)
2011                .argument("group")
2012                .argument("AlbumArtist")
2013                .argument("group")
2014                .argument("Album")
2015        );
2016    }
2017
2018    #[test]
2019    fn command_listallinfo() {
2020        assert_eq!(ListAllIn::root().command(), RawCommand::new("listallinfo"));
2021
2022        assert_eq!(
2023            ListAllIn::directory("foo").command(),
2024            RawCommand::new("listallinfo").argument("foo")
2025        );
2026    }
2027
2028    #[test]
2029    fn command_playlistdelete() {
2030        assert_eq!(
2031            RemoveFromPlaylist::position("foo", 5).command(),
2032            RawCommand::new("playlistdelete")
2033                .argument("foo")
2034                .argument("5"),
2035        );
2036
2037        assert_eq!(
2038            RemoveFromPlaylist::range("foo", SongPosition(3)..SongPosition(6)).command(),
2039            RawCommand::new("playlistdelete")
2040                .argument("foo")
2041                .argument("3:6"),
2042        );
2043    }
2044
2045    #[test]
2046    fn command_tagtypes() {
2047        assert_eq!(
2048            TagTypes::enable_all().command(),
2049            RawCommand::new("tagtypes").argument("all"),
2050        );
2051
2052        assert_eq!(
2053            TagTypes::disable_all().command(),
2054            RawCommand::new("tagtypes").argument("clear"),
2055        );
2056
2057        assert_eq!(
2058            TagTypes::disable(&[Tag::Album, Tag::Title]).command(),
2059            RawCommand::new("tagtypes")
2060                .argument("disable")
2061                .argument("Album")
2062                .argument("Title")
2063        );
2064
2065        assert_eq!(
2066            TagTypes::enable(&[Tag::Album, Tag::Title]).command(),
2067            RawCommand::new("tagtypes")
2068                .argument("enable")
2069                .argument("Album")
2070                .argument("Title")
2071        );
2072    }
2073
2074    #[test]
2075    fn command_get_enabled_tagtypes() {
2076        assert_eq!(GetEnabledTagTypes.command(), RawCommand::new("tagtypes"));
2077    }
2078
2079    #[test]
2080    fn command_sticker_get() {
2081        assert_eq!(
2082            StickerGet::new("foo", "bar").command(),
2083            RawCommand::new("sticker")
2084                .argument("get")
2085                .argument("song")
2086                .argument("foo")
2087                .argument("bar")
2088        );
2089    }
2090
2091    #[test]
2092    fn command_sticker_set() {
2093        assert_eq!(
2094            StickerSet::new("foo", "bar", "baz").command(),
2095            RawCommand::new("sticker")
2096                .argument("set")
2097                .argument("song")
2098                .argument("foo")
2099                .argument("bar")
2100                .argument("baz")
2101        );
2102    }
2103
2104    #[test]
2105    fn command_sticker_delete() {
2106        assert_eq!(
2107            StickerDelete::new("foo", "bar").command(),
2108            RawCommand::new("sticker")
2109                .argument("delete")
2110                .argument("song")
2111                .argument("foo")
2112                .argument("bar")
2113        );
2114    }
2115
2116    #[test]
2117    fn command_sticker_list() {
2118        assert_eq!(
2119            StickerList::new("foo").command(),
2120            RawCommand::new("sticker")
2121                .argument("list")
2122                .argument("song")
2123                .argument("foo")
2124        );
2125    }
2126
2127    #[test]
2128    fn command_sticker_find() {
2129        assert_eq!(
2130            StickerFind::new("foo", "bar").command(),
2131            RawCommand::new("sticker")
2132                .argument("find")
2133                .argument("song")
2134                .argument("foo")
2135                .argument("bar")
2136        );
2137
2138        assert_eq!(
2139            StickerFind::new("foo", "bar").where_eq("baz").command(),
2140            RawCommand::new("sticker")
2141                .argument("find")
2142                .argument("song")
2143                .argument("foo")
2144                .argument("bar")
2145                .argument("=")
2146                .argument("baz")
2147        );
2148    }
2149
2150    #[test]
2151    fn command_update() {
2152        assert_eq!(Update::new().command(), RawCommand::new("update"));
2153
2154        assert_eq!(
2155            Update::new().uri("folder").command(),
2156            RawCommand::new("update").argument("folder")
2157        )
2158    }
2159
2160    #[test]
2161    fn command_rescan() {
2162        assert_eq!(Rescan::new().command(), RawCommand::new("rescan"));
2163
2164        assert_eq!(
2165            Rescan::new().uri("folder").command(),
2166            RawCommand::new("rescan").argument("folder")
2167        )
2168    }
2169
2170    #[test]
2171    fn command_subscribe() {
2172        assert_eq!(
2173            SubscribeToChannel("hello").command(),
2174            RawCommand::new("subscribe").argument("hello")
2175        );
2176    }
2177
2178    #[test]
2179    fn command_unsubscribe() {
2180        assert_eq!(
2181            UnsubscribeFromChannel("hello").command(),
2182            RawCommand::new("unsubscribe").argument("hello")
2183        );
2184    }
2185
2186    #[test]
2187    fn command_read_messages() {
2188        assert_eq!(
2189            ReadChannelMessages.command(),
2190            RawCommand::new("readmessages")
2191        );
2192    }
2193
2194    #[test]
2195    fn command_list_channels() {
2196        assert_eq!(ListChannels.command(), RawCommand::new("channels"));
2197    }
2198
2199    #[test]
2200    fn command_send_message() {
2201        assert_eq!(
2202            SendChannelMessage::new("foo", "bar").command(),
2203            RawCommand::new("sendmessage")
2204                .argument("foo")
2205                .argument("bar")
2206        );
2207    }
2208}