1 /***************************************************************************
2 * Copyright (C) 2008-2016 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
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. *
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. *
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 ***************************************************************************/
33 const char *mpdDirectory(const std::string
&directory
)
35 // MPD <= 0.19 accepts "/" for a root directory whereas later
36 // versions do not, so provide a compatibility layer.
40 return directory
.c_str();
43 template <typename ObjectT
, typename SourceT
>
44 std::function
<bool(typename
MPD::Iterator
<ObjectT
>::State
&)>
45 defaultFetcher(SourceT
*(fetcher
)(mpd_connection
*))
47 return [fetcher
](typename
MPD::Iterator
<ObjectT
>::State
&state
) {
48 auto src
= fetcher(state
.connection());
59 bool fetchItemSong(MPD::SongIterator::State
&state
)
61 auto src
= mpd_recv_entity(state
.connection());
62 while (src
!= nullptr && mpd_entity_get_type(src
) != MPD_ENTITY_TYPE_SONG
)
65 src
= mpd_recv_entity(state
.connection());
69 state
.setObject(mpd_song_dup(mpd_entity_get_song(src
)));
81 void checkConnectionErrors(mpd_connection
*conn
)
83 mpd_error code
= mpd_connection_get_error(conn
);
84 if (code
!= MPD_ERROR_SUCCESS
)
86 std::string msg
= mpd_connection_get_error_message(conn
);
87 if (code
== MPD_ERROR_SERVER
)
89 mpd_server_error server_code
= mpd_connection_get_server_error(conn
);
90 bool clearable
= mpd_connection_clear_error(conn
);
91 throw ServerError(server_code
, msg
, clearable
);
95 bool clearable
= mpd_connection_clear_error(conn
);
96 throw ClientError(code
, msg
, clearable
);
101 Connection::Connection() : m_connection(nullptr),
102 m_command_list_active(false),
110 void Connection::Connect()
112 assert(!m_connection
);
115 m_connection
.reset(mpd_connection_new(m_host
.c_str(), m_port
, m_timeout
* 1000));
117 if (!m_password
.empty())
119 m_fd
= mpd_connection_get_fd(m_connection
.get());
122 catch (MPD::ClientError
&e
)
129 bool Connection::Connected() const
131 return m_connection
.get() != nullptr;
134 void Connection::Disconnect()
136 m_connection
= nullptr;
137 m_command_list_active
= false;
141 unsigned Connection::Version() const
143 return m_connection
? mpd_connection_get_server_version(m_connection
.get())[1] : 0;
146 void Connection::SetHostname(const std::string
&host
)
148 size_t at
= host
.find("@");
149 if (at
!= std::string::npos
)
151 m_password
= host
.substr(0, at
);
152 m_host
= host
.substr(at
+1);
158 void Connection::SendPassword()
160 assert(m_connection
);
162 assert(!m_command_list_active
);
163 mpd_run_password(m_connection
.get(), m_password
.c_str());
167 void Connection::idle()
172 mpd_send_idle(m_connection
.get());
178 int Connection::noidle()
182 if (m_idle
&& mpd_send_noidle(m_connection
.get()))
185 flags
= mpd_recv_idle(m_connection
.get(), true);
186 mpd_response_finish(m_connection
.get());
192 void Connection::setNoidleCallback(NoidleCallback callback
)
194 m_noidle_callback
= std::move(callback
);
197 Statistics
Connection::getStatistics()
200 mpd_stats
*stats
= mpd_run_stats(m_connection
.get());
202 return Statistics(stats
);
205 Status
Connection::getStatus()
208 mpd_status
*status
= mpd_run_status(m_connection
.get());
210 return Status(status
);
213 void Connection::UpdateDirectory(const std::string
&path
)
215 prechecksNoCommandsList();
216 mpd_run_update(m_connection
.get(), path
.c_str());
220 void Connection::Play()
222 prechecksNoCommandsList();
223 mpd_run_play(m_connection
.get());
227 void Connection::Play(int pos
)
229 prechecksNoCommandsList();
230 mpd_run_play_pos(m_connection
.get(), pos
);
234 void Connection::PlayID(int id
)
236 prechecksNoCommandsList();
237 mpd_run_play_id(m_connection
.get(), id
);
241 void Connection::Pause(bool state
)
243 prechecksNoCommandsList();
244 mpd_run_pause(m_connection
.get(), state
);
248 void Connection::Toggle()
250 prechecksNoCommandsList();
251 mpd_run_toggle_pause(m_connection
.get());
255 void Connection::Stop()
257 prechecksNoCommandsList();
258 mpd_run_stop(m_connection
.get());
262 void Connection::Next()
264 prechecksNoCommandsList();
265 mpd_run_next(m_connection
.get());
269 void Connection::Prev()
271 prechecksNoCommandsList();
272 mpd_run_previous(m_connection
.get());
276 void Connection::Move(unsigned from
, unsigned to
)
279 if (m_command_list_active
)
280 mpd_send_move(m_connection
.get(), from
, to
);
283 mpd_run_move(m_connection
.get(), from
, to
);
288 void Connection::Swap(unsigned from
, unsigned to
)
291 if (m_command_list_active
)
292 mpd_send_swap(m_connection
.get(), from
, to
);
295 mpd_run_swap(m_connection
.get(), from
, to
);
300 void Connection::Seek(unsigned pos
, unsigned where
)
302 prechecksNoCommandsList();
303 mpd_run_seek_pos(m_connection
.get(), pos
, where
);
307 void Connection::Shuffle()
309 prechecksNoCommandsList();
310 mpd_run_shuffle(m_connection
.get());
314 void Connection::ShuffleRange(unsigned start
, unsigned end
)
316 prechecksNoCommandsList();
317 mpd_run_shuffle_range(m_connection
.get(), start
, end
);
321 void Connection::ClearMainPlaylist()
323 prechecksNoCommandsList();
324 mpd_run_clear(m_connection
.get());
328 void Connection::ClearPlaylist(const std::string
&playlist
)
330 prechecksNoCommandsList();
331 mpd_run_playlist_clear(m_connection
.get(), playlist
.c_str());
335 void Connection::AddToPlaylist(const std::string
&path
, const Song
&s
)
337 AddToPlaylist(path
, s
.getURI());
340 void Connection::AddToPlaylist(const std::string
&path
, const std::string
&file
)
343 if (m_command_list_active
)
344 mpd_send_playlist_add(m_connection
.get(), path
.c_str(), file
.c_str());
347 mpd_run_playlist_add(m_connection
.get(), path
.c_str(), file
.c_str());
352 void Connection::PlaylistMove(const std::string
&path
, int from
, int to
)
355 if (m_command_list_active
)
356 mpd_send_playlist_move(m_connection
.get(), path
.c_str(), from
, to
);
359 mpd_send_playlist_move(m_connection
.get(), path
.c_str(), from
, to
);
360 mpd_response_finish(m_connection
.get());
365 void Connection::Rename(const std::string
&from
, const std::string
&to
)
367 prechecksNoCommandsList();
368 mpd_run_rename(m_connection
.get(), from
.c_str(), to
.c_str());
372 SongIterator
Connection::GetPlaylistChanges(unsigned version
)
374 prechecksNoCommandsList();
375 mpd_send_queue_changes_meta(m_connection
.get(), version
);
377 return SongIterator(m_connection
.get(), defaultFetcher
<Song
>(mpd_recv_song
));
380 Song
Connection::GetCurrentSong()
382 prechecksNoCommandsList();
383 mpd_send_current_song(m_connection
.get());
384 mpd_song
*s
= mpd_recv_song(m_connection
.get());
385 mpd_response_finish(m_connection
.get());
387 // currentsong doesn't return error if there is no playing song.
394 Song
Connection::GetSong(const std::string
&path
)
396 prechecksNoCommandsList();
397 mpd_send_list_all_meta(m_connection
.get(), path
.c_str());
398 mpd_song
*s
= mpd_recv_song(m_connection
.get());
399 mpd_response_finish(m_connection
.get());
404 SongIterator
Connection::GetPlaylistContent(const std::string
&path
)
406 prechecksNoCommandsList();
407 mpd_send_list_playlist_meta(m_connection
.get(), path
.c_str());
408 SongIterator
result(m_connection
.get(), defaultFetcher
<Song
>(mpd_recv_song
));
413 SongIterator
Connection::GetPlaylistContentNoInfo(const std::string
&path
)
415 prechecksNoCommandsList();
416 mpd_send_list_playlist(m_connection
.get(), path
.c_str());
417 SongIterator
result(m_connection
.get(), defaultFetcher
<Song
>(mpd_recv_song
));
422 StringIterator
Connection::GetSupportedExtensions()
424 prechecksNoCommandsList();
425 mpd_send_command(m_connection
.get(), "decoders", NULL
);
427 return StringIterator(m_connection
.get(), [](StringIterator::State
&state
) {
428 auto src
= mpd_recv_pair_named(state
.connection(), "suffix");
431 state
.setObject(src
->value
);
432 mpd_return_pair(state
.connection(), src
);
440 void Connection::SetRepeat(bool mode
)
442 prechecksNoCommandsList();
443 mpd_run_repeat(m_connection
.get(), mode
);
447 void Connection::SetRandom(bool mode
)
449 prechecksNoCommandsList();
450 mpd_run_random(m_connection
.get(), mode
);
454 void Connection::SetSingle(bool mode
)
456 prechecksNoCommandsList();
457 mpd_run_single(m_connection
.get(), mode
);
461 void Connection::SetConsume(bool mode
)
463 prechecksNoCommandsList();
464 mpd_run_consume(m_connection
.get(), mode
);
468 void Connection::SetVolume(unsigned vol
)
470 prechecksNoCommandsList();
471 mpd_run_set_volume(m_connection
.get(), vol
);
475 void Connection::ChangeVolume(int change
)
477 prechecksNoCommandsList();
478 mpd_run_change_volume(m_connection
.get(), change
);
483 std::string
Connection::GetReplayGainMode()
485 prechecksNoCommandsList();
486 mpd_send_command(m_connection
.get(), "replay_gain_status", NULL
);
488 if (mpd_pair
*pair
= mpd_recv_pair_named(m_connection
.get(), "replay_gain_mode"))
490 result
= pair
->value
;
491 mpd_return_pair(m_connection
.get(), pair
);
493 mpd_response_finish(m_connection
.get());
498 void Connection::SetReplayGainMode(ReplayGainMode mode
)
500 prechecksNoCommandsList();
517 mpd_send_command(m_connection
.get(), "replay_gain_mode", rg_mode
, NULL
);
518 mpd_response_finish(m_connection
.get());
522 void Connection::SetCrossfade(unsigned crossfade
)
524 prechecksNoCommandsList();
525 mpd_run_crossfade(m_connection
.get(), crossfade
);
529 void Connection::SetPriority(const Song
&s
, int prio
)
532 if (m_command_list_active
)
533 mpd_send_prio_id(m_connection
.get(), prio
, s
.getID());
536 mpd_run_prio_id(m_connection
.get(), prio
, s
.getID());
541 int Connection::AddSong(const std::string
&path
, int pos
)
546 mpd_send_add_id(m_connection
.get(), path
.c_str());
548 mpd_send_add_id_to(m_connection
.get(), path
.c_str(), pos
);
549 if (!m_command_list_active
)
551 id
= mpd_recv_song_id(m_connection
.get());
552 mpd_response_finish(m_connection
.get());
560 int Connection::AddSong(const Song
&s
, int pos
)
562 return AddSong((!s
.isFromDatabase() ? "file://" : "") + s
.getURI(), pos
);
565 void Connection::Add(const std::string
&path
)
568 if (m_command_list_active
)
569 mpd_send_add(m_connection
.get(), path
.c_str());
572 mpd_run_add(m_connection
.get(), path
.c_str());
577 bool Connection::AddRandomTag(mpd_tag_type tag
, size_t number
, std::mt19937
&rng
)
579 std::vector
<std::string
> tags(
580 std::make_move_iterator(GetList(tag
)),
581 std::make_move_iterator(StringIterator())
583 if (number
> tags
.size())
586 std::shuffle(tags
.begin(), tags
.end(), rng
);
587 auto it
= tags
.begin();
588 for (size_t i
= 0; i
< number
&& it
!= tags
.end(); ++i
)
591 AddSearch(tag
, *it
++);
592 std::vector
<std::string
> paths
;
593 MPD::SongIterator s
= CommitSearchSongs(), end
;
594 for (; s
!= end
; ++s
)
595 paths
.push_back(s
->getURI());
597 for (const auto &path
: paths
)
599 CommitCommandsList();
604 bool Connection::AddRandomSongs(size_t number
, std::mt19937
&rng
)
606 prechecksNoCommandsList();
607 std::vector
<std::string
> files
;
608 mpd_send_list_all(m_connection
.get(), "/");
609 while (mpd_pair
*item
= mpd_recv_pair_named(m_connection
.get(), "file"))
611 files
.push_back(item
->value
);
612 mpd_return_pair(m_connection
.get(), item
);
614 mpd_response_finish(m_connection
.get());
617 if (number
> files
.size())
619 //if (itsErrorHandler)
620 // itsErrorHandler(this, 0, "Requested number of random songs is bigger than size of your library", itsErrorHandlerUserdata);
625 std::shuffle(files
.begin(), files
.end(), rng
);
627 auto it
= files
.begin();
628 for (size_t i
= 0; i
< number
&& it
!= files
.end(); ++i
, ++it
)
630 CommitCommandsList();
635 void Connection::Delete(unsigned pos
)
638 mpd_send_delete(m_connection
.get(), pos
);
639 if (!m_command_list_active
)
641 mpd_response_finish(m_connection
.get());
646 void Connection::PlaylistDelete(const std::string
&playlist
, unsigned pos
)
649 mpd_send_playlist_delete(m_connection
.get(), playlist
.c_str(), pos
);
650 if (!m_command_list_active
)
652 mpd_response_finish(m_connection
.get());
657 void Connection::StartCommandsList()
659 prechecksNoCommandsList();
660 mpd_command_list_begin(m_connection
.get(), true);
661 m_command_list_active
= true;
665 void Connection::CommitCommandsList()
668 assert(m_command_list_active
);
669 mpd_command_list_end(m_connection
.get());
670 mpd_response_finish(m_connection
.get());
671 m_command_list_active
= false;
675 void Connection::DeletePlaylist(const std::string
&name
)
677 prechecksNoCommandsList();
678 mpd_run_rm(m_connection
.get(), name
.c_str());
682 void Connection::LoadPlaylist(const std::string
&name
)
684 prechecksNoCommandsList();
685 mpd_run_load(m_connection
.get(), name
.c_str());
689 void Connection::SavePlaylist(const std::string
&name
)
691 prechecksNoCommandsList();
692 mpd_send_save(m_connection
.get(), name
.c_str());
693 mpd_response_finish(m_connection
.get());
697 PlaylistIterator
Connection::GetPlaylists()
699 prechecksNoCommandsList();
700 mpd_send_list_playlists(m_connection
.get());
702 return PlaylistIterator(m_connection
.get(), defaultFetcher
<Playlist
>(mpd_recv_playlist
));
705 StringIterator
Connection::GetList(mpd_tag_type type
)
707 prechecksNoCommandsList();
708 mpd_search_db_tags(m_connection
.get(), type
);
709 mpd_search_commit(m_connection
.get());
711 return StringIterator(m_connection
.get(), [type
](StringIterator::State
&state
) {
712 auto src
= mpd_recv_pair_tag(state
.connection(), type
);
715 state
.setObject(src
->value
);
716 mpd_return_pair(state
.connection(), src
);
724 void Connection::StartSearch(bool exact_match
)
726 prechecksNoCommandsList();
727 mpd_search_db_songs(m_connection
.get(), exact_match
);
730 void Connection::StartFieldSearch(mpd_tag_type item
)
732 prechecksNoCommandsList();
733 mpd_search_db_tags(m_connection
.get(), item
);
736 void Connection::AddSearch(mpd_tag_type item
, const std::string
&str
) const
739 mpd_search_add_tag_constraint(m_connection
.get(), MPD_OPERATOR_DEFAULT
, item
, str
.c_str());
742 void Connection::AddSearchAny(const std::string
&str
) const
745 mpd_search_add_any_tag_constraint(m_connection
.get(), MPD_OPERATOR_DEFAULT
, str
.c_str());
748 void Connection::AddSearchURI(const std::string
&str
) const
751 mpd_search_add_uri_constraint(m_connection
.get(), MPD_OPERATOR_DEFAULT
, str
.c_str());
754 SongIterator
Connection::CommitSearchSongs()
756 prechecksNoCommandsList();
757 mpd_search_commit(m_connection
.get());
759 return SongIterator(m_connection
.get(), defaultFetcher
<Song
>(mpd_recv_song
));
762 ItemIterator
Connection::GetDirectory(const std::string
&directory
)
764 prechecksNoCommandsList();
765 mpd_send_list_meta(m_connection
.get(), mpdDirectory(directory
));
767 return ItemIterator(m_connection
.get(), defaultFetcher
<Item
>(mpd_recv_entity
));
770 SongIterator
Connection::GetDirectoryRecursive(const std::string
&directory
)
772 prechecksNoCommandsList();
773 mpd_send_list_all_meta(m_connection
.get(), mpdDirectory(directory
));
775 return SongIterator(m_connection
.get(), fetchItemSong
);
778 DirectoryIterator
Connection::GetDirectories(const std::string
&directory
)
780 prechecksNoCommandsList();
781 mpd_send_list_meta(m_connection
.get(), mpdDirectory(directory
));
783 return DirectoryIterator(m_connection
.get(), defaultFetcher
<Directory
>(mpd_recv_directory
));
786 SongIterator
Connection::GetSongs(const std::string
&directory
)
788 prechecksNoCommandsList();
789 mpd_send_list_meta(m_connection
.get(), mpdDirectory(directory
));
791 return SongIterator(m_connection
.get(), defaultFetcher
<Song
>(mpd_recv_song
));
794 OutputIterator
Connection::GetOutputs()
796 prechecksNoCommandsList();
797 mpd_send_outputs(m_connection
.get());
799 return OutputIterator(m_connection
.get(), defaultFetcher
<Output
>(mpd_recv_output
));
802 void Connection::EnableOutput(int id
)
804 prechecksNoCommandsList();
805 mpd_run_enable_output(m_connection
.get(), id
);
809 void Connection::DisableOutput(int id
)
811 prechecksNoCommandsList();
812 mpd_run_disable_output(m_connection
.get(), id
);
816 StringIterator
Connection::GetURLHandlers()
818 prechecksNoCommandsList();
819 mpd_send_list_url_schemes(m_connection
.get());
821 return StringIterator(m_connection
.get(), [](StringIterator::State
&state
) {
822 auto src
= mpd_recv_pair_named(state
.connection(), "handler");
825 state
.setObject(src
->value
);
826 mpd_return_pair(state
.connection(), src
);
834 StringIterator
Connection::GetTagTypes()
837 prechecksNoCommandsList();
838 mpd_send_list_tag_types(m_connection
.get());
840 return StringIterator(m_connection
.get(), [](StringIterator::State
&state
) {
841 auto src
= mpd_recv_pair_named(state
.connection(), "tagtype");
844 state
.setObject(src
->value
);
845 mpd_return_pair(state
.connection(), src
);
853 void Connection::checkConnection() const
856 throw ClientError(MPD_ERROR_STATE
, "No active MPD connection", false);
859 void Connection::prechecks()
862 int flags
= noidle();
863 if (flags
&& m_noidle_callback
)
864 m_noidle_callback(flags
);
867 void Connection::prechecksNoCommandsList()
869 assert(!m_command_list_active
);
873 void Connection::checkErrors() const
875 checkConnectionErrors(m_connection
.get());