1 /***************************************************************************
2 * Copyright (C) 2008-2021 by Andrzej Rybczak *
3 * andrzej@rybczak.net *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
20
21 #include <cassert>
22 #include <cstdlib>
23 #include <algorithm>
24 #include <map>
25 #include <boost/regex.hpp>
26
27 #include "charset.h"
28 #include "mpdpp.h"
29
30 MPD::Connection Mpd;
31
32 namespace {
33
mpdDirectory(const std::string & directory)34 const char *mpdDirectory(const std::string &directory)
35 {
36 // MPD <= 0.19 accepts "/" for a root directory whereas later
37 // versions do not, so provide a compatibility layer.
38 if (directory == "/")
39 return "";
40 else
41 return directory.c_str();
42 }
43
44 template <typename ObjectT, typename SourceT>
45 std::function<bool(typename MPD::Iterator<ObjectT>::State &)>
defaultFetcher(SourceT * (fetcher)(mpd_connection *))46 defaultFetcher(SourceT *(fetcher)(mpd_connection *))
47 {
48 return [fetcher](typename MPD::Iterator<ObjectT>::State &state) {
49 auto src = fetcher(state.connection());
50 if (src != nullptr)
51 {
52 state.setObject(src);
53 return true;
54 }
55 else
56 return false;
57 };
58 }
59
fetchItemSong(MPD::SongIterator::State & state)60 bool fetchItemSong(MPD::SongIterator::State &state)
61 {
62 auto src = mpd_recv_entity(state.connection());
63 while (src != nullptr && mpd_entity_get_type(src) != MPD_ENTITY_TYPE_SONG)
64 {
65 mpd_entity_free(src);
66 src = mpd_recv_entity(state.connection());
67 }
68 if (src != nullptr)
69 {
70 state.setObject(mpd_song_dup(mpd_entity_get_song(src)));
71 mpd_entity_free(src);
72 return true;
73 }
74 else
75 return false;
76 }
77
78 }
79
80 namespace MPD {
81
checkConnectionErrors(mpd_connection * conn)82 void checkConnectionErrors(mpd_connection *conn)
83 {
84 mpd_error code = mpd_connection_get_error(conn);
85 if (code != MPD_ERROR_SUCCESS)
86 {
87 std::string msg = mpd_connection_get_error_message(conn);
88 if (code == MPD_ERROR_SERVER)
89 {
90 mpd_server_error server_code = mpd_connection_get_server_error(conn);
91 bool clearable = mpd_connection_clear_error(conn);
92 throw ServerError(server_code, msg, clearable);
93 }
94 else
95 {
96 bool clearable = mpd_connection_clear_error(conn);
97 throw ClientError(code, msg, clearable);
98 }
99 }
100 }
101
Connection()102 Connection::Connection() : m_connection(nullptr),
103 m_command_list_active(false),
104 m_idle(false),
105 m_host("localhost"),
106 m_port(6600),
107 m_timeout(15)
108 {
109 }
110
Connect()111 void Connection::Connect()
112 {
113 assert(!m_connection);
114 try
115 {
116 m_connection.reset(mpd_connection_new(m_host.c_str(), m_port, m_timeout * 1000));
117 checkErrors();
118 if (!m_password.empty())
119 SendPassword();
120 m_fd = mpd_connection_get_fd(m_connection.get());
121 checkErrors();
122 }
123 catch (MPD::ClientError &e)
124 {
125 Disconnect();
126 throw e;
127 }
128 }
129
Connected() const130 bool Connection::Connected() const
131 {
132 return m_connection.get() != nullptr;
133 }
134
Disconnect()135 void Connection::Disconnect()
136 {
137 m_connection = nullptr;
138 m_command_list_active = false;
139 m_idle = false;
140 }
141
Version() const142 unsigned Connection::Version() const
143 {
144 return m_connection ? mpd_connection_get_server_version(m_connection.get())[1] : 0;
145 }
146
SetHostname(const std::string & host)147 void Connection::SetHostname(const std::string &host)
148 {
149 size_t at = host.find("@");
150 if (at != std::string::npos)
151 {
152 m_password = host.substr(0, at);
153 m_host = host.substr(at+1);
154 }
155 else
156 m_host = host;
157 }
158
SendPassword()159 void Connection::SendPassword()
160 {
161 assert(m_connection);
162 noidle();
163 assert(!m_command_list_active);
164 mpd_run_password(m_connection.get(), m_password.c_str());
165 checkErrors();
166 }
167
idle()168 void Connection::idle()
169 {
170 checkConnection();
171 if (!m_idle)
172 {
173 mpd_send_idle(m_connection.get());
174 checkErrors();
175 }
176 m_idle = true;
177 }
178
noidle()179 int Connection::noidle()
180 {
181 checkConnection();
182 int flags = 0;
183 if (m_idle && mpd_send_noidle(m_connection.get()))
184 {
185 m_idle = false;
186 flags = mpd_recv_idle(m_connection.get(), true);
187 mpd_response_finish(m_connection.get());
188 checkErrors();
189 }
190 return flags;
191 }
192
setNoidleCallback(NoidleCallback callback)193 void Connection::setNoidleCallback(NoidleCallback callback)
194 {
195 m_noidle_callback = std::move(callback);
196 }
197
getStatistics()198 Statistics Connection::getStatistics()
199 {
200 prechecks();
201 mpd_stats *stats = mpd_run_stats(m_connection.get());
202 checkErrors();
203 return Statistics(stats);
204 }
205
getStatus()206 Status Connection::getStatus()
207 {
208 prechecks();
209 mpd_status *status = mpd_run_status(m_connection.get());
210 checkErrors();
211 return Status(status);
212 }
213
UpdateDirectory(const std::string & path)214 void Connection::UpdateDirectory(const std::string &path)
215 {
216 prechecksNoCommandsList();
217 // Use update as mpd_run_update doesn't call mpd_response_finish if the id
218 // returned from mpd_recv_update_id is 0 which breaks mopidy.
219 mpd_send_update(m_connection.get(), path.c_str());
220 mpd_recv_update_id(m_connection.get());
221 mpd_response_finish(m_connection.get());
222 checkErrors();
223 }
224
Play()225 void Connection::Play()
226 {
227 prechecksNoCommandsList();
228 mpd_run_play(m_connection.get());
229 checkErrors();
230 }
231
Play(int pos)232 void Connection::Play(int pos)
233 {
234 prechecksNoCommandsList();
235 mpd_run_play_pos(m_connection.get(), pos);
236 checkErrors();
237 }
238
PlayID(int id)239 void Connection::PlayID(int id)
240 {
241 prechecksNoCommandsList();
242 mpd_run_play_id(m_connection.get(), id);
243 checkErrors();
244 }
245
Pause(bool state)246 void Connection::Pause(bool state)
247 {
248 prechecksNoCommandsList();
249 mpd_run_pause(m_connection.get(), state);
250 checkErrors();
251 }
252
Toggle()253 void Connection::Toggle()
254 {
255 prechecksNoCommandsList();
256 mpd_run_toggle_pause(m_connection.get());
257 checkErrors();
258 }
259
Stop()260 void Connection::Stop()
261 {
262 prechecksNoCommandsList();
263 mpd_run_stop(m_connection.get());
264 checkErrors();
265 }
266
Next()267 void Connection::Next()
268 {
269 prechecksNoCommandsList();
270 mpd_run_next(m_connection.get());
271 checkErrors();
272 }
273
Prev()274 void Connection::Prev()
275 {
276 prechecksNoCommandsList();
277 mpd_run_previous(m_connection.get());
278 checkErrors();
279 }
280
Move(unsigned from,unsigned to)281 void Connection::Move(unsigned from, unsigned to)
282 {
283 prechecks();
284 if (m_command_list_active)
285 mpd_send_move(m_connection.get(), from, to);
286 else
287 {
288 mpd_run_move(m_connection.get(), from, to);
289 checkErrors();
290 }
291 }
292
Swap(unsigned from,unsigned to)293 void Connection::Swap(unsigned from, unsigned to)
294 {
295 prechecks();
296 if (m_command_list_active)
297 mpd_send_swap(m_connection.get(), from, to);
298 else
299 {
300 mpd_run_swap(m_connection.get(), from, to);
301 checkErrors();
302 }
303 }
304
Seek(unsigned pos,unsigned where)305 void Connection::Seek(unsigned pos, unsigned where)
306 {
307 prechecksNoCommandsList();
308 mpd_run_seek_pos(m_connection.get(), pos, where);
309 checkErrors();
310 }
311
Shuffle()312 void Connection::Shuffle()
313 {
314 prechecksNoCommandsList();
315 mpd_run_shuffle(m_connection.get());
316 checkErrors();
317 }
318
ShuffleRange(unsigned start,unsigned end)319 void Connection::ShuffleRange(unsigned start, unsigned end)
320 {
321 prechecksNoCommandsList();
322 mpd_run_shuffle_range(m_connection.get(), start, end);
323 checkErrors();
324 }
325
ClearMainPlaylist()326 void Connection::ClearMainPlaylist()
327 {
328 prechecksNoCommandsList();
329 mpd_run_clear(m_connection.get());
330 checkErrors();
331 }
332
ClearPlaylist(const std::string & playlist)333 void Connection::ClearPlaylist(const std::string &playlist)
334 {
335 prechecksNoCommandsList();
336 mpd_run_playlist_clear(m_connection.get(), playlist.c_str());
337 checkErrors();
338 }
339
AddToPlaylist(const std::string & path,const Song & s)340 void Connection::AddToPlaylist(const std::string &path, const Song &s)
341 {
342 AddToPlaylist(path, s.getURI());
343 }
344
AddToPlaylist(const std::string & path,const std::string & file)345 void Connection::AddToPlaylist(const std::string &path, const std::string &file)
346 {
347 prechecks();
348 if (m_command_list_active)
349 mpd_send_playlist_add(m_connection.get(), path.c_str(), file.c_str());
350 else
351 {
352 mpd_run_playlist_add(m_connection.get(), path.c_str(), file.c_str());
353 checkErrors();
354 }
355 }
356
PlaylistMove(const std::string & path,int from,int to)357 void Connection::PlaylistMove(const std::string &path, int from, int to)
358 {
359 prechecks();
360 if (m_command_list_active)
361 mpd_send_playlist_move(m_connection.get(), path.c_str(), from, to);
362 else
363 {
364 mpd_send_playlist_move(m_connection.get(), path.c_str(), from, to);
365 mpd_response_finish(m_connection.get());
366 checkErrors();
367 }
368 }
369
Rename(const std::string & from,const std::string & to)370 void Connection::Rename(const std::string &from, const std::string &to)
371 {
372 prechecksNoCommandsList();
373 mpd_run_rename(m_connection.get(), from.c_str(), to.c_str());
374 checkErrors();
375 }
376
GetPlaylistChanges(unsigned version)377 SongIterator Connection::GetPlaylistChanges(unsigned version)
378 {
379 prechecksNoCommandsList();
380 mpd_send_queue_changes_meta(m_connection.get(), version);
381 checkErrors();
382 return SongIterator(m_connection.get(), defaultFetcher<Song>(mpd_recv_song));
383 }
384
GetCurrentSong()385 Song Connection::GetCurrentSong()
386 {
387 prechecksNoCommandsList();
388 mpd_send_current_song(m_connection.get());
389 mpd_song *s = mpd_recv_song(m_connection.get());
390 mpd_response_finish(m_connection.get());
391 checkErrors();
392 // currentsong doesn't return error if there is no playing song.
393 if (s == nullptr)
394 return Song();
395 else
396 return Song(s);
397 }
398
GetSong(const std::string & path)399 Song Connection::GetSong(const std::string &path)
400 {
401 prechecksNoCommandsList();
402 mpd_send_list_all_meta(m_connection.get(), path.c_str());
403 mpd_song *s = mpd_recv_song(m_connection.get());
404 mpd_response_finish(m_connection.get());
405 checkErrors();
406 return Song(s);
407 }
408
GetPlaylistContent(const std::string & path)409 SongIterator Connection::GetPlaylistContent(const std::string &path)
410 {
411 prechecksNoCommandsList();
412 mpd_send_list_playlist_meta(m_connection.get(), path.c_str());
413 SongIterator result(m_connection.get(), defaultFetcher<Song>(mpd_recv_song));
414 checkErrors();
415 return result;
416 }
417
GetPlaylistContentNoInfo(const std::string & path)418 SongIterator Connection::GetPlaylistContentNoInfo(const std::string &path)
419 {
420 prechecksNoCommandsList();
421 mpd_send_list_playlist(m_connection.get(), path.c_str());
422 SongIterator result(m_connection.get(), defaultFetcher<Song>(mpd_recv_song));
423 checkErrors();
424 return result;
425 }
426
GetSupportedExtensions()427 StringIterator Connection::GetSupportedExtensions()
428 {
429 prechecksNoCommandsList();
430 mpd_send_command(m_connection.get(), "decoders", NULL);
431 checkErrors();
432 return StringIterator(m_connection.get(), [](StringIterator::State &state) {
433 auto src = mpd_recv_pair_named(state.connection(), "suffix");
434 if (src != nullptr)
435 {
436 state.setObject(src->value);
437 mpd_return_pair(state.connection(), src);
438 return true;
439 }
440 else
441 return false;
442 });
443 }
444
SetRepeat(bool mode)445 void Connection::SetRepeat(bool mode)
446 {
447 prechecksNoCommandsList();
448 mpd_run_repeat(m_connection.get(), mode);
449 checkErrors();
450 }
451
SetRandom(bool mode)452 void Connection::SetRandom(bool mode)
453 {
454 prechecksNoCommandsList();
455 mpd_run_random(m_connection.get(), mode);
456 checkErrors();
457 }
458
SetSingle(bool mode)459 void Connection::SetSingle(bool mode)
460 {
461 prechecksNoCommandsList();
462 mpd_run_single(m_connection.get(), mode);
463 checkErrors();
464 }
465
SetConsume(bool mode)466 void Connection::SetConsume(bool mode)
467 {
468 prechecksNoCommandsList();
469 mpd_run_consume(m_connection.get(), mode);
470 checkErrors();
471 }
472
SetVolume(unsigned vol)473 void Connection::SetVolume(unsigned vol)
474 {
475 prechecksNoCommandsList();
476 mpd_run_set_volume(m_connection.get(), vol);
477 checkErrors();
478 }
479
ChangeVolume(int change)480 void Connection::ChangeVolume(int change)
481 {
482 prechecksNoCommandsList();
483 mpd_run_change_volume(m_connection.get(), change);
484 checkErrors();
485 }
486
487
GetReplayGainMode()488 std::string Connection::GetReplayGainMode()
489 {
490 prechecksNoCommandsList();
491 mpd_send_command(m_connection.get(), "replay_gain_status", NULL);
492 std::string result;
493 if (mpd_pair *pair = mpd_recv_pair_named(m_connection.get(), "replay_gain_mode"))
494 {
495 result = pair->value;
496 mpd_return_pair(m_connection.get(), pair);
497 }
498 mpd_response_finish(m_connection.get());
499 checkErrors();
500 return result;
501 }
502
SetReplayGainMode(ReplayGainMode mode)503 void Connection::SetReplayGainMode(ReplayGainMode mode)
504 {
505 prechecksNoCommandsList();
506 const char *rg_mode;
507 switch (mode)
508 {
509 case rgmOff:
510 rg_mode = "off";
511 break;
512 case rgmTrack:
513 rg_mode = "track";
514 break;
515 case rgmAlbum:
516 rg_mode = "album";
517 break;
518 default:
519 rg_mode = "";
520 break;
521 }
522 mpd_send_command(m_connection.get(), "replay_gain_mode", rg_mode, NULL);
523 mpd_response_finish(m_connection.get());
524 checkErrors();
525 }
526
SetCrossfade(unsigned crossfade)527 void Connection::SetCrossfade(unsigned crossfade)
528 {
529 prechecksNoCommandsList();
530 mpd_run_crossfade(m_connection.get(), crossfade);
531 checkErrors();
532 }
533
SetPriority(const Song & s,int prio)534 void Connection::SetPriority(const Song &s, int prio)
535 {
536 prechecks();
537 if (m_command_list_active)
538 mpd_send_prio_id(m_connection.get(), prio, s.getID());
539 else
540 {
541 mpd_run_prio_id(m_connection.get(), prio, s.getID());
542 checkErrors();
543 }
544 }
545
AddSong(const std::string & path,int pos)546 int Connection::AddSong(const std::string &path, int pos)
547 {
548 prechecks();
549 int id;
550 if (pos < 0)
551 mpd_send_add_id(m_connection.get(), path.c_str());
552 else
553 mpd_send_add_id_to(m_connection.get(), path.c_str(), pos);
554 if (!m_command_list_active)
555 {
556 id = mpd_recv_song_id(m_connection.get());
557 mpd_response_finish(m_connection.get());
558 checkErrors();
559 }
560 else
561 id = 0;
562 return id;
563 }
564
AddSong(const Song & s,int pos)565 int Connection::AddSong(const Song &s, int pos)
566 {
567 return AddSong((!s.isFromDatabase() ? "file://" : "") + s.getURI(), pos);
568 }
569
Add(const std::string & path)570 bool Connection::Add(const std::string &path)
571 {
572 bool result;
573 prechecks();
574 if (m_command_list_active)
575 result = mpd_send_add(m_connection.get(), path.c_str());
576 else
577 {
578 result = mpd_run_add(m_connection.get(), path.c_str());
579 checkErrors();
580 }
581 return result;
582 }
583
AddRandomTag(mpd_tag_type tag,size_t number,std::mt19937 & rng)584 bool Connection::AddRandomTag(mpd_tag_type tag, size_t number, std::mt19937 &rng)
585 {
586 std::vector<std::string> tags(
587 std::make_move_iterator(GetList(tag)),
588 std::make_move_iterator(StringIterator())
589 );
590 if (number > tags.size())
591 return false;
592
593 std::shuffle(tags.begin(), tags.end(), rng);
594 auto it = tags.begin();
595 for (size_t i = 0; i < number && it != tags.end(); ++i)
596 {
597 StartSearch(true);
598 AddSearch(tag, *it++);
599 std::vector<std::string> paths;
600 MPD::SongIterator s = CommitSearchSongs(), end;
601 for (; s != end; ++s)
602 paths.push_back(s->getURI());
603 StartCommandsList();
604 for (const auto &path : paths)
605 AddSong(path);
606 CommitCommandsList();
607 }
608 return true;
609 }
610
AddRandomSongs(size_t number,const std::string & random_exclude_pattern,std::mt19937 & rng)611 bool Connection::AddRandomSongs(size_t number, const std::string &random_exclude_pattern, std::mt19937 &rng)
612 {
613 prechecksNoCommandsList();
614 std::vector<std::string> files;
615 mpd_send_list_all(m_connection.get(), "/");
616 while (mpd_pair *item = mpd_recv_pair_named(m_connection.get(), "file"))
617 {
618 files.push_back(item->value);
619 mpd_return_pair(m_connection.get(), item);
620 }
621 mpd_response_finish(m_connection.get());
622 checkErrors();
623
624 if (number > files.size())
625 {
626 //if (itsErrorHandler)
627 // itsErrorHandler(this, 0, "Requested number of random songs is bigger than size of your library", itsErrorHandlerUserdata);
628 return false;
629 }
630 else
631 {
632 std::shuffle(files.begin(), files.end(), rng);
633 StartCommandsList();
634 auto it = files.begin();
635 boost::regex re(random_exclude_pattern);
636 for (size_t i = 0; i < number && it != files.end(); ++it) {
637 if (random_exclude_pattern.empty() || !boost::regex_match((*it), re)) {
638 AddSong(*it);
639 i++;
640 }
641 }
642 CommitCommandsList();
643 }
644 return true;
645 }
646
Delete(unsigned pos)647 void Connection::Delete(unsigned pos)
648 {
649 prechecks();
650 mpd_send_delete(m_connection.get(), pos);
651 if (!m_command_list_active)
652 {
653 mpd_response_finish(m_connection.get());
654 checkErrors();
655 }
656 }
657
DeleteRange(unsigned begin,unsigned end)658 void Connection::DeleteRange(unsigned begin, unsigned end)
659 {
660 prechecks();
661 mpd_send_delete_range(m_connection.get(), begin, end);
662 if (!m_command_list_active)
663 {
664 mpd_response_finish(m_connection.get());
665 checkErrors();
666 }
667 }
668
PlaylistDelete(const std::string & playlist,unsigned pos)669 void Connection::PlaylistDelete(const std::string &playlist, unsigned pos)
670 {
671 prechecks();
672 mpd_send_playlist_delete(m_connection.get(), playlist.c_str(), pos);
673 if (!m_command_list_active)
674 {
675 mpd_response_finish(m_connection.get());
676 checkErrors();
677 }
678 }
679
StartCommandsList()680 void Connection::StartCommandsList()
681 {
682 prechecksNoCommandsList();
683 mpd_command_list_begin(m_connection.get(), true);
684 m_command_list_active = true;
685 checkErrors();
686 }
687
CommitCommandsList()688 void Connection::CommitCommandsList()
689 {
690 prechecks();
691 assert(m_command_list_active);
692 mpd_command_list_end(m_connection.get());
693 mpd_response_finish(m_connection.get());
694 m_command_list_active = false;
695 checkErrors();
696 }
697
DeletePlaylist(const std::string & name)698 void Connection::DeletePlaylist(const std::string &name)
699 {
700 prechecksNoCommandsList();
701 mpd_run_rm(m_connection.get(), name.c_str());
702 checkErrors();
703 }
704
LoadPlaylist(const std::string & name)705 bool Connection::LoadPlaylist(const std::string &name)
706 {
707 prechecksNoCommandsList();
708 bool result = mpd_run_load(m_connection.get(), name.c_str());
709 checkErrors();
710 return result;
711 }
712
SavePlaylist(const std::string & name)713 void Connection::SavePlaylist(const std::string &name)
714 {
715 prechecksNoCommandsList();
716 mpd_send_save(m_connection.get(), name.c_str());
717 mpd_response_finish(m_connection.get());
718 checkErrors();
719 }
720
GetPlaylists()721 PlaylistIterator Connection::GetPlaylists()
722 {
723 prechecksNoCommandsList();
724 mpd_send_list_playlists(m_connection.get());
725 checkErrors();
726 return PlaylistIterator(m_connection.get(), defaultFetcher<Playlist>(mpd_recv_playlist));
727 }
728
GetList(mpd_tag_type type)729 StringIterator Connection::GetList(mpd_tag_type type)
730 {
731 prechecksNoCommandsList();
732 mpd_search_db_tags(m_connection.get(), type);
733 mpd_search_commit(m_connection.get());
734 checkErrors();
735 return StringIterator(m_connection.get(), [type](StringIterator::State &state) {
736 auto src = mpd_recv_pair_tag(state.connection(), type);
737 if (src != nullptr)
738 {
739 state.setObject(src->value);
740 mpd_return_pair(state.connection(), src);
741 return true;
742 }
743 else
744 return false;
745 });
746 }
747
StartSearch(bool exact_match)748 void Connection::StartSearch(bool exact_match)
749 {
750 prechecksNoCommandsList();
751 mpd_search_db_songs(m_connection.get(), exact_match);
752 }
753
StartFieldSearch(mpd_tag_type item)754 void Connection::StartFieldSearch(mpd_tag_type item)
755 {
756 prechecksNoCommandsList();
757 mpd_search_db_tags(m_connection.get(), item);
758 }
759
AddSearch(mpd_tag_type item,const std::string & str) const760 void Connection::AddSearch(mpd_tag_type item, const std::string &str) const
761 {
762 checkConnection();
763 mpd_search_add_tag_constraint(m_connection.get(), MPD_OPERATOR_DEFAULT, item, str.c_str());
764 }
765
AddSearchAny(const std::string & str) const766 void Connection::AddSearchAny(const std::string &str) const
767 {
768 checkConnection();
769 mpd_search_add_any_tag_constraint(m_connection.get(), MPD_OPERATOR_DEFAULT, str.c_str());
770 }
771
AddSearchURI(const std::string & str) const772 void Connection::AddSearchURI(const std::string &str) const
773 {
774 checkConnection();
775 mpd_search_add_uri_constraint(m_connection.get(), MPD_OPERATOR_DEFAULT, str.c_str());
776 }
777
CommitSearchSongs()778 SongIterator Connection::CommitSearchSongs()
779 {
780 prechecksNoCommandsList();
781 mpd_search_commit(m_connection.get());
782 checkErrors();
783 return SongIterator(m_connection.get(), defaultFetcher<Song>(mpd_recv_song));
784 }
785
GetDirectory(const std::string & directory)786 ItemIterator Connection::GetDirectory(const std::string &directory)
787 {
788 prechecksNoCommandsList();
789 mpd_send_list_meta(m_connection.get(), mpdDirectory(directory));
790 checkErrors();
791 return ItemIterator(m_connection.get(), defaultFetcher<Item>(mpd_recv_entity));
792 }
793
GetDirectoryRecursive(const std::string & directory)794 SongIterator Connection::GetDirectoryRecursive(const std::string &directory)
795 {
796 prechecksNoCommandsList();
797 mpd_send_list_all_meta(m_connection.get(), mpdDirectory(directory));
798 checkErrors();
799 return SongIterator(m_connection.get(), fetchItemSong);
800 }
801
GetDirectories(const std::string & directory)802 DirectoryIterator Connection::GetDirectories(const std::string &directory)
803 {
804 prechecksNoCommandsList();
805 mpd_send_list_meta(m_connection.get(), mpdDirectory(directory));
806 checkErrors();
807 return DirectoryIterator(m_connection.get(), defaultFetcher<Directory>(mpd_recv_directory));
808 }
809
GetSongs(const std::string & directory)810 SongIterator Connection::GetSongs(const std::string &directory)
811 {
812 prechecksNoCommandsList();
813 mpd_send_list_meta(m_connection.get(), mpdDirectory(directory));
814 checkErrors();
815 return SongIterator(m_connection.get(), defaultFetcher<Song>(mpd_recv_song));
816 }
817
GetOutputs()818 OutputIterator Connection::GetOutputs()
819 {
820 prechecksNoCommandsList();
821 mpd_send_outputs(m_connection.get());
822 checkErrors();
823 return OutputIterator(m_connection.get(), defaultFetcher<Output>(mpd_recv_output));
824 }
825
EnableOutput(int id)826 void Connection::EnableOutput(int id)
827 {
828 prechecksNoCommandsList();
829 mpd_run_enable_output(m_connection.get(), id);
830 checkErrors();
831 }
832
DisableOutput(int id)833 void Connection::DisableOutput(int id)
834 {
835 prechecksNoCommandsList();
836 mpd_run_disable_output(m_connection.get(), id);
837 checkErrors();
838 }
839
GetURLHandlers()840 StringIterator Connection::GetURLHandlers()
841 {
842 prechecksNoCommandsList();
843 mpd_send_list_url_schemes(m_connection.get());
844 checkErrors();
845 return StringIterator(m_connection.get(), [](StringIterator::State &state) {
846 auto src = mpd_recv_pair_named(state.connection(), "handler");
847 if (src != nullptr)
848 {
849 state.setObject(src->value);
850 mpd_return_pair(state.connection(), src);
851 return true;
852 }
853 else
854 return false;
855 });
856 }
857
GetTagTypes()858 StringIterator Connection::GetTagTypes()
859 {
860
861 prechecksNoCommandsList();
862 mpd_send_list_tag_types(m_connection.get());
863 checkErrors();
864 return StringIterator(m_connection.get(), [](StringIterator::State &state) {
865 auto src = mpd_recv_pair_named(state.connection(), "tagtype");
866 if (src != nullptr)
867 {
868 state.setObject(src->value);
869 mpd_return_pair(state.connection(), src);
870 return true;
871 }
872 else
873 return false;
874 });
875 }
876
checkConnection() const877 void Connection::checkConnection() const
878 {
879 if (!m_connection)
880 throw ClientError(MPD_ERROR_STATE, "No active MPD connection", false);
881 }
882
prechecks()883 void Connection::prechecks()
884 {
885 checkConnection();
886 int flags = noidle();
887 if (flags && m_noidle_callback)
888 m_noidle_callback(flags);
889 }
890
prechecksNoCommandsList()891 void Connection::prechecksNoCommandsList()
892 {
893 assert(!m_command_list_active);
894 prechecks();
895 }
896
checkErrors() const897 void Connection::checkErrors() const
898 {
899 checkConnectionErrors(m_connection.get());
900 }
901
902 }
903