1 /***************************************************************************
2 * Copyright (C) 2008-2014 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 template <typename ObjectT
, typename SourceT
>
34 std::function
<bool(typename
MPD::Iterator
<ObjectT
>::State
&)>
35 defaultFetcher(SourceT
*(fetcher
)(mpd_connection
*))
37 return [fetcher
](typename
MPD::Iterator
<ObjectT
>::State
&state
) {
38 auto src
= fetcher(state
.connection());
49 bool fetchItemSong(MPD::SongIterator::State
&state
)
51 auto src
= mpd_recv_entity(state
.connection());
52 while (src
!= nullptr && mpd_entity_get_type(src
) != MPD_ENTITY_TYPE_SONG
)
55 src
= mpd_recv_entity(state
.connection());
59 state
.setObject(mpd_song_dup(mpd_entity_get_song(src
)));
71 void checkConnectionErrors(mpd_connection
*conn
)
73 mpd_error code
= mpd_connection_get_error(conn
);
74 if (code
!= MPD_ERROR_SUCCESS
)
76 std::string msg
= mpd_connection_get_error_message(conn
);
77 if (code
== MPD_ERROR_SERVER
)
79 mpd_server_error server_code
= mpd_connection_get_server_error(conn
);
80 bool clearable
= mpd_connection_clear_error(conn
);
81 throw ServerError(server_code
, msg
, clearable
);
85 bool clearable
= mpd_connection_clear_error(conn
);
86 throw ClientError(code
, msg
, clearable
);
91 Connection::Connection() : m_connection(nullptr),
92 m_command_list_active(false),
100 void Connection::Connect()
102 assert(!m_connection
);
105 m_connection
.reset(mpd_connection_new(m_host
.c_str(), m_port
, m_timeout
* 1000));
107 if (!m_password
.empty())
109 m_fd
= mpd_connection_get_fd(m_connection
.get());
112 catch (MPD::ClientError
&e
)
119 bool Connection::Connected() const
121 return m_connection
.get() != nullptr;
124 void Connection::Disconnect()
126 m_connection
= nullptr;
127 m_command_list_active
= false;
131 unsigned Connection::Version() const
133 return m_connection
? mpd_connection_get_server_version(m_connection
.get())[1] : 0;
136 void Connection::SetHostname(const std::string
&host
)
138 size_t at
= host
.find("@");
139 if (at
!= std::string::npos
)
141 m_password
= host
.substr(0, at
);
142 m_host
= host
.substr(at
+1);
148 void Connection::SendPassword()
150 assert(m_connection
);
152 assert(!m_command_list_active
);
153 mpd_run_password(m_connection
.get(), m_password
.c_str());
157 void Connection::idle()
162 mpd_send_idle(m_connection
.get());
168 int Connection::noidle()
172 if (m_idle
&& mpd_send_noidle(m_connection
.get()))
175 flags
= mpd_recv_idle(m_connection
.get(), true);
176 mpd_response_finish(m_connection
.get());
182 Statistics
Connection::getStatistics()
185 mpd_stats
*stats
= mpd_run_stats(m_connection
.get());
187 return Statistics(stats
);
190 Status
Connection::getStatus()
193 mpd_status
*status
= mpd_run_status(m_connection
.get());
195 return Status(status
);
198 void Connection::UpdateDirectory(const std::string
&path
)
200 prechecksNoCommandsList();
201 mpd_run_update(m_connection
.get(), path
.c_str());
205 void Connection::Play()
207 prechecksNoCommandsList();
208 mpd_run_play(m_connection
.get());
212 void Connection::Play(int pos
)
214 prechecksNoCommandsList();
215 mpd_run_play_pos(m_connection
.get(), pos
);
219 void Connection::PlayID(int id
)
221 prechecksNoCommandsList();
222 mpd_run_play_id(m_connection
.get(), id
);
226 void Connection::Pause(bool state
)
228 prechecksNoCommandsList();
229 mpd_run_pause(m_connection
.get(), state
);
233 void Connection::Toggle()
235 prechecksNoCommandsList();
236 mpd_run_toggle_pause(m_connection
.get());
240 void Connection::Stop()
242 prechecksNoCommandsList();
243 mpd_run_stop(m_connection
.get());
247 void Connection::Next()
249 prechecksNoCommandsList();
250 mpd_run_next(m_connection
.get());
254 void Connection::Prev()
256 prechecksNoCommandsList();
257 mpd_run_previous(m_connection
.get());
261 void Connection::Move(unsigned from
, unsigned to
)
264 if (m_command_list_active
)
265 mpd_send_move(m_connection
.get(), from
, to
);
268 mpd_run_move(m_connection
.get(), from
, to
);
273 void Connection::Swap(unsigned from
, unsigned to
)
276 if (m_command_list_active
)
277 mpd_send_swap(m_connection
.get(), from
, to
);
280 mpd_run_swap(m_connection
.get(), from
, to
);
285 void Connection::Seek(unsigned pos
, unsigned where
)
287 prechecksNoCommandsList();
288 mpd_run_seek_pos(m_connection
.get(), pos
, where
);
292 void Connection::Shuffle()
294 prechecksNoCommandsList();
295 mpd_run_shuffle(m_connection
.get());
299 void Connection::ShuffleRange(unsigned start
, unsigned end
)
301 prechecksNoCommandsList();
302 mpd_run_shuffle_range(m_connection
.get(), start
, end
);
306 void Connection::ClearMainPlaylist()
308 prechecksNoCommandsList();
309 mpd_run_clear(m_connection
.get());
313 void Connection::ClearPlaylist(const std::string
&playlist
)
315 prechecksNoCommandsList();
316 mpd_run_playlist_clear(m_connection
.get(), playlist
.c_str());
320 void Connection::AddToPlaylist(const std::string
&path
, const Song
&s
)
322 AddToPlaylist(path
, s
.getURI());
325 void Connection::AddToPlaylist(const std::string
&path
, const std::string
&file
)
328 if (m_command_list_active
)
329 mpd_send_playlist_add(m_connection
.get(), path
.c_str(), file
.c_str());
332 mpd_run_playlist_add(m_connection
.get(), path
.c_str(), file
.c_str());
337 void Connection::PlaylistMove(const std::string
&path
, int from
, int to
)
340 if (m_command_list_active
)
341 mpd_send_playlist_move(m_connection
.get(), path
.c_str(), from
, to
);
344 mpd_send_playlist_move(m_connection
.get(), path
.c_str(), from
, to
);
345 mpd_response_finish(m_connection
.get());
350 void Connection::Rename(const std::string
&from
, const std::string
&to
)
352 prechecksNoCommandsList();
353 mpd_run_rename(m_connection
.get(), from
.c_str(), to
.c_str());
357 SongIterator
Connection::GetPlaylistChanges(unsigned version
)
359 prechecksNoCommandsList();
360 mpd_send_queue_changes_meta(m_connection
.get(), version
);
362 return SongIterator(m_connection
.get(), defaultFetcher
<Song
>(mpd_recv_song
));
365 Song
Connection::GetCurrentSong()
367 prechecksNoCommandsList();
368 mpd_send_current_song(m_connection
.get());
369 mpd_song
*s
= mpd_recv_song(m_connection
.get());
370 mpd_response_finish(m_connection
.get());
375 Song
Connection::GetSong(const std::string
&path
)
377 prechecksNoCommandsList();
378 mpd_send_list_all_meta(m_connection
.get(), path
.c_str());
379 mpd_song
*s
= mpd_recv_song(m_connection
.get());
380 mpd_response_finish(m_connection
.get());
385 SongIterator
Connection::GetPlaylistContent(const std::string
&path
)
387 prechecksNoCommandsList();
388 mpd_send_list_playlist_meta(m_connection
.get(), path
.c_str());
389 SongIterator
result(m_connection
.get(), defaultFetcher
<Song
>(mpd_recv_song
));
394 SongIterator
Connection::GetPlaylistContentNoInfo(const std::string
&path
)
396 prechecksNoCommandsList();
397 mpd_send_list_playlist(m_connection
.get(), path
.c_str());
398 SongIterator
result(m_connection
.get(), defaultFetcher
<Song
>(mpd_recv_song
));
403 StringIterator
Connection::GetSupportedExtensions()
405 prechecksNoCommandsList();
406 mpd_send_command(m_connection
.get(), "decoders", NULL
);
408 return StringIterator(m_connection
.get(), [](StringIterator::State
&state
) {
409 auto src
= mpd_recv_pair_named(state
.connection(), "suffix");
412 state
.setObject(src
->value
);
413 mpd_return_pair(state
.connection(), src
);
421 void Connection::SetRepeat(bool mode
)
423 prechecksNoCommandsList();
424 mpd_run_repeat(m_connection
.get(), mode
);
428 void Connection::SetRandom(bool mode
)
430 prechecksNoCommandsList();
431 mpd_run_random(m_connection
.get(), mode
);
435 void Connection::SetSingle(bool mode
)
437 prechecksNoCommandsList();
438 mpd_run_single(m_connection
.get(), mode
);
442 void Connection::SetConsume(bool mode
)
444 prechecksNoCommandsList();
445 mpd_run_consume(m_connection
.get(), mode
);
449 void Connection::SetVolume(unsigned vol
)
451 prechecksNoCommandsList();
452 mpd_run_set_volume(m_connection
.get(), vol
);
456 std::string
Connection::GetReplayGainMode()
458 prechecksNoCommandsList();
459 mpd_send_command(m_connection
.get(), "replay_gain_status", NULL
);
461 if (mpd_pair
*pair
= mpd_recv_pair_named(m_connection
.get(), "replay_gain_mode"))
463 result
= pair
->value
;
464 mpd_return_pair(m_connection
.get(), pair
);
466 mpd_response_finish(m_connection
.get());
471 void Connection::SetReplayGainMode(ReplayGainMode mode
)
473 prechecksNoCommandsList();
490 mpd_send_command(m_connection
.get(), "replay_gain_mode", rg_mode
, NULL
);
491 mpd_response_finish(m_connection
.get());
495 void Connection::SetCrossfade(unsigned crossfade
)
497 prechecksNoCommandsList();
498 mpd_run_crossfade(m_connection
.get(), crossfade
);
502 void Connection::SetPriority(const Song
&s
, int prio
)
505 if (m_command_list_active
)
506 mpd_send_prio_id(m_connection
.get(), prio
, s
.getID());
509 mpd_run_prio_id(m_connection
.get(), prio
, s
.getID());
514 int Connection::AddSong(const std::string
&path
, int pos
)
519 mpd_send_add_id(m_connection
.get(), path
.c_str());
521 mpd_send_add_id_to(m_connection
.get(), path
.c_str(), pos
);
522 if (!m_command_list_active
)
524 id
= mpd_recv_song_id(m_connection
.get());
525 mpd_response_finish(m_connection
.get());
533 int Connection::AddSong(const Song
&s
, int pos
)
535 return AddSong((!s
.isFromDatabase() ? "file://" : "") + s
.getURI(), pos
);
538 void Connection::Add(const std::string
&path
)
541 if (m_command_list_active
)
542 mpd_send_add(m_connection
.get(), path
.c_str());
545 mpd_run_add(m_connection
.get(), path
.c_str());
550 bool Connection::AddRandomTag(mpd_tag_type tag
, size_t number
)
552 std::vector
<std::string
> tags(
553 std::make_move_iterator(GetList(tag
)),
554 std::make_move_iterator(StringIterator())
556 if (number
> tags
.size())
559 std::random_shuffle(tags
.begin(), tags
.end());
560 auto it
= tags
.begin();
561 for (size_t i
= 0; i
< number
&& it
!= tags
.end(); ++i
)
564 AddSearch(tag
, *it
++);
565 std::vector
<std::string
> paths
;
566 MPD::SongIterator s
= CommitSearchSongs(), end
;
567 for (; s
!= end
; ++s
)
568 paths
.push_back(s
->getURI());
570 for (const auto &path
: paths
)
572 CommitCommandsList();
577 bool Connection::AddRandomSongs(size_t number
)
579 prechecksNoCommandsList();
580 std::vector
<std::string
> files
;
581 mpd_send_list_all(m_connection
.get(), "/");
582 while (mpd_pair
*item
= mpd_recv_pair_named(m_connection
.get(), "file"))
584 files
.push_back(item
->value
);
585 mpd_return_pair(m_connection
.get(), item
);
587 mpd_response_finish(m_connection
.get());
590 if (number
> files
.size())
592 //if (itsErrorHandler)
593 // itsErrorHandler(this, 0, "Requested number of random songs is bigger than size of your library", itsErrorHandlerUserdata);
598 std::random_shuffle(files
.begin(), files
.end());
600 auto it
= files
.begin();
601 for (size_t i
= 0; i
< number
&& it
!= files
.end(); ++i
, ++it
)
603 CommitCommandsList();
608 void Connection::Delete(unsigned pos
)
611 mpd_send_delete(m_connection
.get(), pos
);
612 if (!m_command_list_active
)
614 mpd_response_finish(m_connection
.get());
619 void Connection::PlaylistDelete(const std::string
&playlist
, unsigned pos
)
622 mpd_send_playlist_delete(m_connection
.get(), playlist
.c_str(), pos
);
623 if (!m_command_list_active
)
625 mpd_response_finish(m_connection
.get());
630 void Connection::StartCommandsList()
632 prechecksNoCommandsList();
633 mpd_command_list_begin(m_connection
.get(), true);
634 m_command_list_active
= true;
638 void Connection::CommitCommandsList()
641 assert(m_command_list_active
);
642 mpd_command_list_end(m_connection
.get());
643 mpd_response_finish(m_connection
.get());
644 m_command_list_active
= false;
648 void Connection::DeletePlaylist(const std::string
&name
)
650 prechecksNoCommandsList();
651 mpd_run_rm(m_connection
.get(), name
.c_str());
655 void Connection::LoadPlaylist(const std::string
&name
)
657 prechecksNoCommandsList();
658 mpd_run_load(m_connection
.get(), name
.c_str());
662 void Connection::SavePlaylist(const std::string
&name
)
664 prechecksNoCommandsList();
665 mpd_send_save(m_connection
.get(), name
.c_str());
666 mpd_response_finish(m_connection
.get());
670 PlaylistIterator
Connection::GetPlaylists()
672 prechecksNoCommandsList();
673 mpd_send_list_playlists(m_connection
.get());
675 return PlaylistIterator(m_connection
.get(), defaultFetcher
<Playlist
>(mpd_recv_playlist
));
678 StringIterator
Connection::GetList(mpd_tag_type type
)
680 prechecksNoCommandsList();
681 mpd_search_db_tags(m_connection
.get(), type
);
682 mpd_search_commit(m_connection
.get());
684 return StringIterator(m_connection
.get(), [type
](StringIterator::State
&state
) {
685 auto src
= mpd_recv_pair_tag(state
.connection(), type
);
688 state
.setObject(src
->value
);
689 mpd_return_pair(state
.connection(), src
);
697 void Connection::StartSearch(bool exact_match
)
699 prechecksNoCommandsList();
700 mpd_search_db_songs(m_connection
.get(), exact_match
);
703 void Connection::StartFieldSearch(mpd_tag_type item
)
705 prechecksNoCommandsList();
706 mpd_search_db_tags(m_connection
.get(), item
);
709 void Connection::AddSearch(mpd_tag_type item
, const std::string
&str
) const
712 mpd_search_add_tag_constraint(m_connection
.get(), MPD_OPERATOR_DEFAULT
, item
, str
.c_str());
715 void Connection::AddSearchAny(const std::string
&str
) const
718 mpd_search_add_any_tag_constraint(m_connection
.get(), MPD_OPERATOR_DEFAULT
, str
.c_str());
721 void Connection::AddSearchURI(const std::string
&str
) const
724 mpd_search_add_uri_constraint(m_connection
.get(), MPD_OPERATOR_DEFAULT
, str
.c_str());
727 SongIterator
Connection::CommitSearchSongs()
729 prechecksNoCommandsList();
730 mpd_search_commit(m_connection
.get());
732 return SongIterator(m_connection
.get(), defaultFetcher
<Song
>(mpd_recv_song
));
735 ItemIterator
Connection::GetDirectory(const std::string
&directory
)
737 prechecksNoCommandsList();
738 mpd_send_list_meta(m_connection
.get(), directory
.c_str());
740 return ItemIterator(m_connection
.get(), defaultFetcher
<Item
>(mpd_recv_entity
));
743 SongIterator
Connection::GetDirectoryRecursive(const std::string
&directory
)
745 prechecksNoCommandsList();
746 mpd_send_list_all_meta(m_connection
.get(), directory
.c_str());
748 return SongIterator(m_connection
.get(), fetchItemSong
);
751 DirectoryIterator
Connection::GetDirectories(const std::string
&directory
)
753 prechecksNoCommandsList();
754 mpd_send_list_meta(m_connection
.get(), directory
.c_str());
756 return DirectoryIterator(m_connection
.get(), defaultFetcher
<Directory
>(mpd_recv_directory
));
759 SongIterator
Connection::GetSongs(const std::string
&directory
)
761 prechecksNoCommandsList();
762 mpd_send_list_meta(m_connection
.get(), directory
.c_str());
764 return SongIterator(m_connection
.get(), defaultFetcher
<Song
>(mpd_recv_song
));
767 OutputIterator
Connection::GetOutputs()
769 prechecksNoCommandsList();
770 mpd_send_outputs(m_connection
.get());
772 return OutputIterator(m_connection
.get(), defaultFetcher
<Output
>(mpd_recv_output
));
775 void Connection::EnableOutput(int id
)
777 prechecksNoCommandsList();
778 mpd_run_enable_output(m_connection
.get(), id
);
782 void Connection::DisableOutput(int id
)
784 prechecksNoCommandsList();
785 mpd_run_disable_output(m_connection
.get(), id
);
789 StringIterator
Connection::GetURLHandlers()
791 prechecksNoCommandsList();
792 mpd_send_list_url_schemes(m_connection
.get());
794 return StringIterator(m_connection
.get(), [](StringIterator::State
&state
) {
795 auto src
= mpd_recv_pair_named(state
.connection(), "handler");
798 state
.setObject(src
->value
);
799 mpd_return_pair(state
.connection(), src
);
807 StringIterator
Connection::GetTagTypes()
810 prechecksNoCommandsList();
811 mpd_send_list_tag_types(m_connection
.get());
813 return StringIterator(m_connection
.get(), [](StringIterator::State
&state
) {
814 auto src
= mpd_recv_pair_named(state
.connection(), "tagtype");
817 state
.setObject(src
->value
);
818 mpd_return_pair(state
.connection(), src
);
826 void Connection::checkConnection() const
829 throw ClientError(MPD_ERROR_STATE
, "No active MPD connection", false);
832 void Connection::prechecks()
838 void Connection::prechecksNoCommandsList()
840 assert(!m_command_list_active
);
844 void Connection::checkErrors() const
846 checkConnectionErrors(m_connection
.get());