Fix InternetLyricsFetcher
[ncmpcpp.git] / src / mpdpp.cpp
blob3b199af7dfe89d87335c1af8f5ab235ab74bea11
1 /***************************************************************************
2 * Copyright (C) 2008-2017 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
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 ***************************************************************************/
21 #include <cassert>
22 #include <cstdlib>
23 #include <algorithm>
24 #include <map>
26 #include "charset.h"
27 #include "mpdpp.h"
29 MPD::Connection Mpd;
31 namespace {
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.
37 if (directory == "/")
38 return "";
39 else
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());
49 if (src != nullptr)
51 state.setObject(src);
52 return true;
54 else
55 return false;
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)
64 mpd_entity_free(src);
65 src = mpd_recv_entity(state.connection());
67 if (src != nullptr)
69 state.setObject(mpd_song_dup(mpd_entity_get_song(src)));
70 mpd_entity_free(src);
71 return true;
73 else
74 return false;
79 namespace MPD {
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);
93 else
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),
103 m_idle(false),
104 m_host("localhost"),
105 m_port(6600),
106 m_timeout(15)
110 void Connection::Connect()
112 assert(!m_connection);
115 m_connection.reset(mpd_connection_new(m_host.c_str(), m_port, m_timeout * 1000));
116 checkErrors();
117 if (!m_password.empty())
118 SendPassword();
119 m_fd = mpd_connection_get_fd(m_connection.get());
120 checkErrors();
122 catch (MPD::ClientError &e)
124 Disconnect();
125 throw 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;
138 m_idle = 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);
154 else
155 m_host = host;
158 void Connection::SendPassword()
160 assert(m_connection);
161 noidle();
162 assert(!m_command_list_active);
163 mpd_run_password(m_connection.get(), m_password.c_str());
164 checkErrors();
167 void Connection::idle()
169 checkConnection();
170 if (!m_idle)
172 mpd_send_idle(m_connection.get());
173 checkErrors();
175 m_idle = true;
178 int Connection::noidle()
180 checkConnection();
181 int flags = 0;
182 if (m_idle && mpd_send_noidle(m_connection.get()))
184 m_idle = false;
185 flags = mpd_recv_idle(m_connection.get(), true);
186 mpd_response_finish(m_connection.get());
187 checkErrors();
189 return flags;
192 void Connection::setNoidleCallback(NoidleCallback callback)
194 m_noidle_callback = std::move(callback);
197 Statistics Connection::getStatistics()
199 prechecks();
200 mpd_stats *stats = mpd_run_stats(m_connection.get());
201 checkErrors();
202 return Statistics(stats);
205 Status Connection::getStatus()
207 prechecks();
208 mpd_status *status = mpd_run_status(m_connection.get());
209 checkErrors();
210 return Status(status);
213 void Connection::UpdateDirectory(const std::string &path)
215 prechecksNoCommandsList();
216 mpd_run_update(m_connection.get(), path.c_str());
217 checkErrors();
220 void Connection::Play()
222 prechecksNoCommandsList();
223 mpd_run_play(m_connection.get());
224 checkErrors();
227 void Connection::Play(int pos)
229 prechecksNoCommandsList();
230 mpd_run_play_pos(m_connection.get(), pos);
231 checkErrors();
234 void Connection::PlayID(int id)
236 prechecksNoCommandsList();
237 mpd_run_play_id(m_connection.get(), id);
238 checkErrors();
241 void Connection::Pause(bool state)
243 prechecksNoCommandsList();
244 mpd_run_pause(m_connection.get(), state);
245 checkErrors();
248 void Connection::Toggle()
250 prechecksNoCommandsList();
251 mpd_run_toggle_pause(m_connection.get());
252 checkErrors();
255 void Connection::Stop()
257 prechecksNoCommandsList();
258 mpd_run_stop(m_connection.get());
259 checkErrors();
262 void Connection::Next()
264 prechecksNoCommandsList();
265 mpd_run_next(m_connection.get());
266 checkErrors();
269 void Connection::Prev()
271 prechecksNoCommandsList();
272 mpd_run_previous(m_connection.get());
273 checkErrors();
276 void Connection::Move(unsigned from, unsigned to)
278 prechecks();
279 if (m_command_list_active)
280 mpd_send_move(m_connection.get(), from, to);
281 else
283 mpd_run_move(m_connection.get(), from, to);
284 checkErrors();
288 void Connection::Swap(unsigned from, unsigned to)
290 prechecks();
291 if (m_command_list_active)
292 mpd_send_swap(m_connection.get(), from, to);
293 else
295 mpd_run_swap(m_connection.get(), from, to);
296 checkErrors();
300 void Connection::Seek(unsigned pos, unsigned where)
302 prechecksNoCommandsList();
303 mpd_run_seek_pos(m_connection.get(), pos, where);
304 checkErrors();
307 void Connection::Shuffle()
309 prechecksNoCommandsList();
310 mpd_run_shuffle(m_connection.get());
311 checkErrors();
314 void Connection::ShuffleRange(unsigned start, unsigned end)
316 prechecksNoCommandsList();
317 mpd_run_shuffle_range(m_connection.get(), start, end);
318 checkErrors();
321 void Connection::ClearMainPlaylist()
323 prechecksNoCommandsList();
324 mpd_run_clear(m_connection.get());
325 checkErrors();
328 void Connection::ClearPlaylist(const std::string &playlist)
330 prechecksNoCommandsList();
331 mpd_run_playlist_clear(m_connection.get(), playlist.c_str());
332 checkErrors();
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)
342 prechecks();
343 if (m_command_list_active)
344 mpd_send_playlist_add(m_connection.get(), path.c_str(), file.c_str());
345 else
347 mpd_run_playlist_add(m_connection.get(), path.c_str(), file.c_str());
348 checkErrors();
352 void Connection::PlaylistMove(const std::string &path, int from, int to)
354 prechecks();
355 if (m_command_list_active)
356 mpd_send_playlist_move(m_connection.get(), path.c_str(), from, to);
357 else
359 mpd_send_playlist_move(m_connection.get(), path.c_str(), from, to);
360 mpd_response_finish(m_connection.get());
361 checkErrors();
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());
369 checkErrors();
372 SongIterator Connection::GetPlaylistChanges(unsigned version)
374 prechecksNoCommandsList();
375 mpd_send_queue_changes_meta(m_connection.get(), version);
376 checkErrors();
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());
386 checkErrors();
387 // currentsong doesn't return error if there is no playing song.
388 if (s == nullptr)
389 return Song();
390 else
391 return Song(s);
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());
400 checkErrors();
401 return Song(s);
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));
409 checkErrors();
410 return result;
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));
418 checkErrors();
419 return result;
422 StringIterator Connection::GetSupportedExtensions()
424 prechecksNoCommandsList();
425 mpd_send_command(m_connection.get(), "decoders", NULL);
426 checkErrors();
427 return StringIterator(m_connection.get(), [](StringIterator::State &state) {
428 auto src = mpd_recv_pair_named(state.connection(), "suffix");
429 if (src != nullptr)
431 state.setObject(src->value);
432 mpd_return_pair(state.connection(), src);
433 return true;
435 else
436 return false;
440 void Connection::SetRepeat(bool mode)
442 prechecksNoCommandsList();
443 mpd_run_repeat(m_connection.get(), mode);
444 checkErrors();
447 void Connection::SetRandom(bool mode)
449 prechecksNoCommandsList();
450 mpd_run_random(m_connection.get(), mode);
451 checkErrors();
454 void Connection::SetSingle(bool mode)
456 prechecksNoCommandsList();
457 mpd_run_single(m_connection.get(), mode);
458 checkErrors();
461 void Connection::SetConsume(bool mode)
463 prechecksNoCommandsList();
464 mpd_run_consume(m_connection.get(), mode);
465 checkErrors();
468 void Connection::SetVolume(unsigned vol)
470 prechecksNoCommandsList();
471 mpd_run_set_volume(m_connection.get(), vol);
472 checkErrors();
475 void Connection::ChangeVolume(int change)
477 prechecksNoCommandsList();
478 mpd_run_change_volume(m_connection.get(), change);
479 checkErrors();
483 std::string Connection::GetReplayGainMode()
485 prechecksNoCommandsList();
486 mpd_send_command(m_connection.get(), "replay_gain_status", NULL);
487 std::string result;
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());
494 checkErrors();
495 return result;
498 void Connection::SetReplayGainMode(ReplayGainMode mode)
500 prechecksNoCommandsList();
501 const char *rg_mode;
502 switch (mode)
504 case rgmOff:
505 rg_mode = "off";
506 break;
507 case rgmTrack:
508 rg_mode = "track";
509 break;
510 case rgmAlbum:
511 rg_mode = "album";
512 break;
513 default:
514 rg_mode = "";
515 break;
517 mpd_send_command(m_connection.get(), "replay_gain_mode", rg_mode, NULL);
518 mpd_response_finish(m_connection.get());
519 checkErrors();
522 void Connection::SetCrossfade(unsigned crossfade)
524 prechecksNoCommandsList();
525 mpd_run_crossfade(m_connection.get(), crossfade);
526 checkErrors();
529 void Connection::SetPriority(const Song &s, int prio)
531 prechecks();
532 if (m_command_list_active)
533 mpd_send_prio_id(m_connection.get(), prio, s.getID());
534 else
536 mpd_run_prio_id(m_connection.get(), prio, s.getID());
537 checkErrors();
541 int Connection::AddSong(const std::string &path, int pos)
543 prechecks();
544 int id;
545 if (pos < 0)
546 mpd_send_add_id(m_connection.get(), path.c_str());
547 else
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());
553 checkErrors();
555 else
556 id = 0;
557 return id;
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)
567 prechecks();
568 if (m_command_list_active)
569 mpd_send_add(m_connection.get(), path.c_str());
570 else
572 mpd_run_add(m_connection.get(), path.c_str());
573 checkErrors();
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())
584 return false;
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)
590 StartSearch(true);
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());
596 StartCommandsList();
597 for (const auto &path : paths)
598 AddSong(path);
599 CommitCommandsList();
601 return true;
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());
615 checkErrors();
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);
621 return false;
623 else
625 std::shuffle(files.begin(), files.end(), rng);
626 StartCommandsList();
627 auto it = files.begin();
628 for (size_t i = 0; i < number && it != files.end(); ++i, ++it)
629 AddSong(*it);
630 CommitCommandsList();
632 return true;
635 void Connection::Delete(unsigned pos)
637 prechecks();
638 mpd_send_delete(m_connection.get(), pos);
639 if (!m_command_list_active)
641 mpd_response_finish(m_connection.get());
642 checkErrors();
646 void Connection::PlaylistDelete(const std::string &playlist, unsigned pos)
648 prechecks();
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());
653 checkErrors();
657 void Connection::StartCommandsList()
659 prechecksNoCommandsList();
660 mpd_command_list_begin(m_connection.get(), true);
661 m_command_list_active = true;
662 checkErrors();
665 void Connection::CommitCommandsList()
667 prechecks();
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;
672 checkErrors();
675 void Connection::DeletePlaylist(const std::string &name)
677 prechecksNoCommandsList();
678 mpd_run_rm(m_connection.get(), name.c_str());
679 checkErrors();
682 void Connection::LoadPlaylist(const std::string &name)
684 prechecksNoCommandsList();
685 mpd_run_load(m_connection.get(), name.c_str());
686 checkErrors();
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());
694 checkErrors();
697 PlaylistIterator Connection::GetPlaylists()
699 prechecksNoCommandsList();
700 mpd_send_list_playlists(m_connection.get());
701 checkErrors();
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());
710 checkErrors();
711 return StringIterator(m_connection.get(), [type](StringIterator::State &state) {
712 auto src = mpd_recv_pair_tag(state.connection(), type);
713 if (src != nullptr)
715 state.setObject(src->value);
716 mpd_return_pair(state.connection(), src);
717 return true;
719 else
720 return false;
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
738 checkConnection();
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
744 checkConnection();
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
750 checkConnection();
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());
758 checkErrors();
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));
766 checkErrors();
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));
774 checkErrors();
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));
782 checkErrors();
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));
790 checkErrors();
791 return SongIterator(m_connection.get(), defaultFetcher<Song>(mpd_recv_song));
794 OutputIterator Connection::GetOutputs()
796 prechecksNoCommandsList();
797 mpd_send_outputs(m_connection.get());
798 checkErrors();
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);
806 checkErrors();
809 void Connection::DisableOutput(int id)
811 prechecksNoCommandsList();
812 mpd_run_disable_output(m_connection.get(), id);
813 checkErrors();
816 StringIterator Connection::GetURLHandlers()
818 prechecksNoCommandsList();
819 mpd_send_list_url_schemes(m_connection.get());
820 checkErrors();
821 return StringIterator(m_connection.get(), [](StringIterator::State &state) {
822 auto src = mpd_recv_pair_named(state.connection(), "handler");
823 if (src != nullptr)
825 state.setObject(src->value);
826 mpd_return_pair(state.connection(), src);
827 return true;
829 else
830 return false;
834 StringIterator Connection::GetTagTypes()
837 prechecksNoCommandsList();
838 mpd_send_list_tag_types(m_connection.get());
839 checkErrors();
840 return StringIterator(m_connection.get(), [](StringIterator::State &state) {
841 auto src = mpd_recv_pair_named(state.connection(), "tagtype");
842 if (src != nullptr)
844 state.setObject(src->value);
845 mpd_return_pair(state.connection(), src);
846 return true;
848 else
849 return false;
853 void Connection::checkConnection() const
855 if (!m_connection)
856 throw ClientError(MPD_ERROR_STATE, "No active MPD connection", false);
859 void Connection::prechecks()
861 checkConnection();
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);
870 prechecks();
873 void Connection::checkErrors() const
875 checkConnectionErrors(m_connection.get());