1use 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 (#[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 (#[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#[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#[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#[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#[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 pub fn all() -> Queue {
164 Queue
165 }
166
167 pub fn song<S>(song: S) -> QueueRange
169 where
170 S: Into<Song>,
171 {
172 QueueRange(SongOrSongRange::Single(song.into()))
173 }
174
175 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),
238
239 Range(SongRange),
241}
242
243#[derive(Clone, Copy, Debug, PartialEq, Eq)]
247pub struct QueueRange(SongOrSongRange);
248
249impl QueueRange {
250 pub fn song<S>(song: S) -> Self
252 where
253 S: Into<Song>,
254 {
255 Self(SongOrSongRange::Single(song.into()))
256 }
257
258 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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[derive(Clone, Debug, PartialEq, Eq)]
494pub struct Shuffle(Option<SongRange>);
495
496impl Shuffle {
497 pub fn all() -> Self {
499 Self(None)
500 }
501
502 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#[derive(Clone, Debug, PartialEq, Eq)]
530pub struct Play(Option<Song>);
531
532impl Play {
533 pub fn current() -> Self {
535 Self(None)
536 }
537
538 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#[derive(Clone, Debug, PartialEq, Eq)]
585pub struct Add<'a> {
586 uri: &'a str,
587 position: Option<PositionOrRelative>,
588}
589
590impl<'a> Add<'a> {
591 pub fn uri(uri: &'a str) -> Self {
595 Self {
596 uri,
597 position: None,
598 }
599 }
600
601 pub fn at<P: Into<SongPosition>>(mut self, position: P) -> Self {
603 self.position = Some(PositionOrRelative::Absolute(position.into()));
604 self
605 }
606
607 pub fn before_current(mut self, delta: usize) -> Self {
613 self.position = Some(PositionOrRelative::BeforeCurrent(delta));
614 self
615 }
616
617 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#[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 pub fn id(id: SongId) -> Self {
659 Self(Target::Id(id))
660 }
661
662 pub fn position(pos: SongPosition) -> Self {
664 let range = SongRange::new(pos..=pos);
665 Self(Target::Range(range))
666 }
667
668 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#[derive(Clone, Debug, PartialEq, Eq)]
696pub struct Move {
697 from: Target,
698 to: PositionOrRelative,
699}
700
701impl Move {
702 pub fn id(id: SongId) -> MoveBuilder {
704 MoveBuilder(Target::Id(id))
705 }
706
707 pub fn position(position: SongPosition) -> MoveBuilder {
709 MoveBuilder(Target::Range(SongRange::new(position..=position)))
710 }
711
712 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#[must_use]
733#[derive(Clone, Debug, PartialEq, Eq)]
734pub struct MoveBuilder(Target);
735
736impl MoveBuilder {
737 pub fn to_position(self, position: SongPosition) -> Move {
739 Move {
740 from: self.0,
741 to: PositionOrRelative::Absolute(position),
742 }
743 }
744
745 pub fn after_current(self, delta: usize) -> Move {
749 Move {
750 from: self.0,
751 to: PositionOrRelative::AfterCurrent(delta),
752 }
753 }
754
755 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#[derive(Clone, Debug, PartialEq, Eq)]
785pub struct Find {
786 filter: Filter,
787 sort: Option<Tag>,
788 window: Option<SongRange>,
789}
790
791impl Find {
792 pub fn new(filter: Filter) -> Self {
794 Self {
795 filter,
796 sort: None,
797 window: None,
798 }
799 }
800
801 pub fn sort(mut self, sort_by: Tag) -> Self {
813 self.sort = Some(sort_by);
814 self
815 }
816
817 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#[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 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 pub fn filter(mut self, filter: Filter) -> Self {
877 self.filter = Some(filter);
878 self
879 }
880
881 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#[derive(Clone, Debug, PartialEq, Eq)]
918pub struct Count {
919 filter: Filter,
920}
921
922impl Count {
923 pub fn new(filter: Filter) -> Count {
925 Count { filter }
926 }
927
928 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#[derive(Clone, Debug, PartialEq, Eq)]
951pub struct CountGrouped {
952 group_by: Tag,
953 filter: Option<Filter>,
954}
955
956impl CountGrouped {
957 pub fn new(group_by: Tag) -> CountGrouped {
959 CountGrouped {
960 group_by,
961 filter: None,
962 }
963 }
964
965 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#[derive(Clone, Debug, PartialEq, Eq)]
994pub struct RenamePlaylist<'a> {
995 from: &'a str,
996 to: &'a str,
997}
998
999impl<'a> RenamePlaylist<'a> {
1000 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#[derive(Clone, Debug, PartialEq, Eq)]
1022pub struct LoadPlaylist<'a> {
1023 name: &'a str,
1024 range: Option<SongRange>,
1025}
1026
1027impl<'a> LoadPlaylist<'a> {
1028 pub fn name(name: &'a str) -> Self {
1030 Self { name, range: None }
1031 }
1032
1033 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#[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 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 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#[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 pub fn position(playlist: &'a str, position: usize) -> Self {
1126 RemoveFromPlaylist {
1127 playlist,
1128 target: PositionOrRange::Position(position),
1129 }
1130 }
1131
1132 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#[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 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#[derive(Clone, Debug, PartialEq, Eq)]
1193pub struct ListAllIn<'a> {
1194 directory: &'a str,
1195}
1196
1197impl<'a> ListAllIn<'a> {
1198 pub fn root() -> ListAllIn<'static> {
1200 ListAllIn { directory: "" }
1201 }
1202
1203 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#[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#[derive(Clone, Debug, PartialEq, Eq)]
1248pub struct AlbumArt<'a> {
1249 uri: &'a str,
1250 offset: usize,
1251}
1252
1253impl<'a> AlbumArt<'a> {
1254 pub fn new(uri: &'a str) -> Self {
1256 Self { uri, offset: 0 }
1257 }
1258
1259 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#[derive(Clone, Debug, PartialEq, Eq)]
1281pub struct AlbumArtEmbedded<'a> {
1282 uri: &'a str,
1283 offset: usize,
1284}
1285
1286impl<'a> AlbumArtEmbedded<'a> {
1287 pub fn new(uri: &'a str) -> Self {
1289 Self { uri, offset: 0 }
1290 }
1291
1292 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#[derive(Clone, Debug, PartialEq, Eq)]
1314pub struct TagTypes<'a>(TagTypesAction<'a>);
1315
1316impl<'a> TagTypes<'a> {
1317 pub fn enable_all() -> TagTypes<'static> {
1319 TagTypes(TagTypesAction::EnableAll)
1320 }
1321
1322 pub fn disable_all() -> TagTypes<'static> {
1324 TagTypes(TagTypesAction::Clear)
1325 }
1326
1327 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 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#[derive(Clone, Debug, PartialEq, Eq)]
1391pub struct StickerGet<'a> {
1392 uri: &'a str,
1393 name: &'a str,
1394}
1395
1396impl<'a> StickerGet<'a> {
1397 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#[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 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#[derive(Clone, Debug, PartialEq, Eq)]
1453pub struct StickerDelete<'a> {
1454 uri: &'a str,
1455 name: &'a str,
1456}
1457
1458impl<'a> StickerDelete<'a> {
1459 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#[derive(Clone, Debug, PartialEq, Eq)]
1483pub struct StickerList<'a> {
1484 uri: &'a str,
1485}
1486
1487impl<'a> StickerList<'a> {
1488 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#[derive(Clone, Debug, PartialEq, Eq)]
1512enum StickerFindOperator {
1513 Equals,
1515 LessThan,
1517 GreaterThan,
1519}
1520
1521#[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 pub fn new(uri: &'a str, name: &'a str) -> Self {
1532 Self {
1533 uri,
1534 name,
1535 filter: None,
1536 }
1537 }
1538
1539 pub fn where_eq(self, value: &'a str) -> Self {
1541 self.add_filter(StickerFindOperator::Equals, value)
1542 }
1543
1544 pub fn where_gt(self, value: &'a str) -> Self {
1546 self.add_filter(StickerFindOperator::GreaterThan, value)
1547 }
1548
1549 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#[derive(Clone, Debug, PartialEq, Eq)]
1591pub struct Update<'a>(Option<&'a str>);
1592
1593impl<'a> Update<'a> {
1594 pub fn new() -> Self {
1596 Update(None)
1597 }
1598
1599 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#[derive(Clone, Debug, PartialEq, Eq)]
1634pub struct Rescan<'a>(Option<&'a str>);
1635
1636impl<'a> Rescan<'a> {
1637 pub fn new() -> Self {
1639 Rescan(None)
1640 }
1641
1642 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#[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#[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#[derive(Debug, Clone, PartialEq, Eq)]
1715pub struct SendChannelMessage<'a> {
1716 channel: &'a str,
1717 message: &'a str,
1718}
1719
1720impl<'a> SendChannelMessage<'a> {
1721 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}