split PressSpace action into modular pieces
[ncmpcpp.git] / src / mpdpp.cpp
blob87b6ce49ce7f4c958c0e69f1446ccb06c57bbbde
1 /***************************************************************************
2 * Copyright (C) 2008-2014 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 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());
39 if (src != nullptr)
41 state.setObject(src);
42 return true;
44 else
45 return false;
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)
54 mpd_entity_free(src);
55 src = mpd_recv_entity(state.connection());
57 if (src != nullptr)
59 state.setObject(mpd_song_dup(mpd_entity_get_song(src)));
60 mpd_entity_free(src);
61 return true;
63 else
64 return false;
69 namespace MPD {
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);
83 else
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),
93 m_idle(false),
94 m_host("localhost"),
95 m_port(6600),
96 m_timeout(15)
100 void Connection::Connect()
102 assert(!m_connection);
105 m_connection.reset(mpd_connection_new(m_host.c_str(), m_port, m_timeout * 1000));
106 checkErrors();
107 if (!m_password.empty())
108 SendPassword();
109 m_fd = mpd_connection_get_fd(m_connection.get());
110 checkErrors();
112 catch (MPD::ClientError &e)
114 Disconnect();
115 throw 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;
128 m_idle = 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);
144 else
145 m_host = host;
148 void Connection::SendPassword()
150 assert(m_connection);
151 noidle();
152 assert(!m_command_list_active);
153 mpd_run_password(m_connection.get(), m_password.c_str());
154 checkErrors();
157 void Connection::idle()
159 checkConnection();
160 if (!m_idle)
162 mpd_send_idle(m_connection.get());
163 checkErrors();
165 m_idle = true;
168 int Connection::noidle()
170 checkConnection();
171 int flags = 0;
172 if (m_idle && mpd_send_noidle(m_connection.get()))
174 m_idle = false;
175 flags = mpd_recv_idle(m_connection.get(), true);
176 mpd_response_finish(m_connection.get());
177 checkErrors();
179 return flags;
182 Statistics Connection::getStatistics()
184 prechecks();
185 mpd_stats *stats = mpd_run_stats(m_connection.get());
186 checkErrors();
187 return Statistics(stats);
190 Status Connection::getStatus()
192 prechecks();
193 mpd_status *status = mpd_run_status(m_connection.get());
194 checkErrors();
195 return Status(status);
198 void Connection::UpdateDirectory(const std::string &path)
200 prechecksNoCommandsList();
201 mpd_run_update(m_connection.get(), path.c_str());
202 checkErrors();
205 void Connection::Play()
207 prechecksNoCommandsList();
208 mpd_run_play(m_connection.get());
209 checkErrors();
212 void Connection::Play(int pos)
214 prechecksNoCommandsList();
215 mpd_run_play_pos(m_connection.get(), pos);
216 checkErrors();
219 void Connection::PlayID(int id)
221 prechecksNoCommandsList();
222 mpd_run_play_id(m_connection.get(), id);
223 checkErrors();
226 void Connection::Pause(bool state)
228 prechecksNoCommandsList();
229 mpd_run_pause(m_connection.get(), state);
230 checkErrors();
233 void Connection::Toggle()
235 prechecksNoCommandsList();
236 mpd_run_toggle_pause(m_connection.get());
237 checkErrors();
240 void Connection::Stop()
242 prechecksNoCommandsList();
243 mpd_run_stop(m_connection.get());
244 checkErrors();
247 void Connection::Next()
249 prechecksNoCommandsList();
250 mpd_run_next(m_connection.get());
251 checkErrors();
254 void Connection::Prev()
256 prechecksNoCommandsList();
257 mpd_run_previous(m_connection.get());
258 checkErrors();
261 void Connection::Move(unsigned from, unsigned to)
263 prechecks();
264 if (m_command_list_active)
265 mpd_send_move(m_connection.get(), from, to);
266 else
268 mpd_run_move(m_connection.get(), from, to);
269 checkErrors();
273 void Connection::Swap(unsigned from, unsigned to)
275 prechecks();
276 if (m_command_list_active)
277 mpd_send_swap(m_connection.get(), from, to);
278 else
280 mpd_run_swap(m_connection.get(), from, to);
281 checkErrors();
285 void Connection::Seek(unsigned pos, unsigned where)
287 prechecksNoCommandsList();
288 mpd_run_seek_pos(m_connection.get(), pos, where);
289 checkErrors();
292 void Connection::Shuffle()
294 prechecksNoCommandsList();
295 mpd_run_shuffle(m_connection.get());
296 checkErrors();
299 void Connection::ShuffleRange(unsigned start, unsigned end)
301 prechecksNoCommandsList();
302 mpd_run_shuffle_range(m_connection.get(), start, end);
303 checkErrors();
306 void Connection::ClearMainPlaylist()
308 prechecksNoCommandsList();
309 mpd_run_clear(m_connection.get());
310 checkErrors();
313 void Connection::ClearPlaylist(const std::string &playlist)
315 prechecksNoCommandsList();
316 mpd_run_playlist_clear(m_connection.get(), playlist.c_str());
317 checkErrors();
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)
327 prechecks();
328 if (m_command_list_active)
329 mpd_send_playlist_add(m_connection.get(), path.c_str(), file.c_str());
330 else
332 mpd_run_playlist_add(m_connection.get(), path.c_str(), file.c_str());
333 checkErrors();
337 void Connection::PlaylistMove(const std::string &path, int from, int to)
339 prechecks();
340 if (m_command_list_active)
341 mpd_send_playlist_move(m_connection.get(), path.c_str(), from, to);
342 else
344 mpd_send_playlist_move(m_connection.get(), path.c_str(), from, to);
345 mpd_response_finish(m_connection.get());
346 checkErrors();
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());
354 checkErrors();
357 SongIterator Connection::GetPlaylistChanges(unsigned version)
359 prechecksNoCommandsList();
360 mpd_send_queue_changes_meta(m_connection.get(), version);
361 checkErrors();
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());
371 checkErrors();
372 return Song(s);
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());
381 checkErrors();
382 return Song(s);
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));
390 checkErrors();
391 return result;
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));
399 checkErrors();
400 return result;
403 StringIterator Connection::GetSupportedExtensions()
405 prechecksNoCommandsList();
406 mpd_send_command(m_connection.get(), "decoders", NULL);
407 checkErrors();
408 return StringIterator(m_connection.get(), [](StringIterator::State &state) {
409 auto src = mpd_recv_pair_named(state.connection(), "suffix");
410 if (src != nullptr)
412 state.setObject(src->value);
413 mpd_return_pair(state.connection(), src);
414 return true;
416 else
417 return false;
421 void Connection::SetRepeat(bool mode)
423 prechecksNoCommandsList();
424 mpd_run_repeat(m_connection.get(), mode);
425 checkErrors();
428 void Connection::SetRandom(bool mode)
430 prechecksNoCommandsList();
431 mpd_run_random(m_connection.get(), mode);
432 checkErrors();
435 void Connection::SetSingle(bool mode)
437 prechecksNoCommandsList();
438 mpd_run_single(m_connection.get(), mode);
439 checkErrors();
442 void Connection::SetConsume(bool mode)
444 prechecksNoCommandsList();
445 mpd_run_consume(m_connection.get(), mode);
446 checkErrors();
449 void Connection::SetVolume(unsigned vol)
451 prechecksNoCommandsList();
452 mpd_run_set_volume(m_connection.get(), vol);
453 checkErrors();
456 std::string Connection::GetReplayGainMode()
458 prechecksNoCommandsList();
459 mpd_send_command(m_connection.get(), "replay_gain_status", NULL);
460 std::string result;
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());
467 checkErrors();
468 return result;
471 void Connection::SetReplayGainMode(ReplayGainMode mode)
473 prechecksNoCommandsList();
474 const char *rg_mode;
475 switch (mode)
477 case rgmOff:
478 rg_mode = "off";
479 break;
480 case rgmTrack:
481 rg_mode = "track";
482 break;
483 case rgmAlbum:
484 rg_mode = "album";
485 break;
486 default:
487 rg_mode = "";
488 break;
490 mpd_send_command(m_connection.get(), "replay_gain_mode", rg_mode, NULL);
491 mpd_response_finish(m_connection.get());
492 checkErrors();
495 void Connection::SetCrossfade(unsigned crossfade)
497 prechecksNoCommandsList();
498 mpd_run_crossfade(m_connection.get(), crossfade);
499 checkErrors();
502 void Connection::SetPriority(const Song &s, int prio)
504 prechecks();
505 if (m_command_list_active)
506 mpd_send_prio_id(m_connection.get(), prio, s.getID());
507 else
509 mpd_run_prio_id(m_connection.get(), prio, s.getID());
510 checkErrors();
514 int Connection::AddSong(const std::string &path, int pos)
516 prechecks();
517 int id;
518 if (pos < 0)
519 mpd_send_add_id(m_connection.get(), path.c_str());
520 else
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());
526 checkErrors();
528 else
529 id = 0;
530 return id;
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)
540 prechecks();
541 if (m_command_list_active)
542 mpd_send_add(m_connection.get(), path.c_str());
543 else
545 mpd_run_add(m_connection.get(), path.c_str());
546 checkErrors();
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())
557 return false;
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)
563 StartSearch(true);
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());
569 StartCommandsList();
570 for (const auto &path : paths)
571 AddSong(path);
572 CommitCommandsList();
574 return true;
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());
588 checkErrors();
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);
594 return false;
596 else
598 std::random_shuffle(files.begin(), files.end());
599 StartCommandsList();
600 auto it = files.begin();
601 for (size_t i = 0; i < number && it != files.end(); ++i, ++it)
602 AddSong(*it);
603 CommitCommandsList();
605 return true;
608 void Connection::Delete(unsigned pos)
610 prechecks();
611 mpd_send_delete(m_connection.get(), pos);
612 if (!m_command_list_active)
614 mpd_response_finish(m_connection.get());
615 checkErrors();
619 void Connection::PlaylistDelete(const std::string &playlist, unsigned pos)
621 prechecks();
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());
626 checkErrors();
630 void Connection::StartCommandsList()
632 prechecksNoCommandsList();
633 mpd_command_list_begin(m_connection.get(), true);
634 m_command_list_active = true;
635 checkErrors();
638 void Connection::CommitCommandsList()
640 prechecks();
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;
645 checkErrors();
648 void Connection::DeletePlaylist(const std::string &name)
650 prechecksNoCommandsList();
651 mpd_run_rm(m_connection.get(), name.c_str());
652 checkErrors();
655 void Connection::LoadPlaylist(const std::string &name)
657 prechecksNoCommandsList();
658 mpd_run_load(m_connection.get(), name.c_str());
659 checkErrors();
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());
667 checkErrors();
670 PlaylistIterator Connection::GetPlaylists()
672 prechecksNoCommandsList();
673 mpd_send_list_playlists(m_connection.get());
674 checkErrors();
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());
683 checkErrors();
684 return StringIterator(m_connection.get(), [type](StringIterator::State &state) {
685 auto src = mpd_recv_pair_tag(state.connection(), type);
686 if (src != nullptr)
688 state.setObject(src->value);
689 mpd_return_pair(state.connection(), src);
690 return true;
692 else
693 return false;
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
711 checkConnection();
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
717 checkConnection();
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
723 checkConnection();
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());
731 checkErrors();
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());
739 checkErrors();
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());
747 checkErrors();
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());
755 checkErrors();
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());
763 checkErrors();
764 return SongIterator(m_connection.get(), defaultFetcher<Song>(mpd_recv_song));
767 OutputIterator Connection::GetOutputs()
769 prechecksNoCommandsList();
770 mpd_send_outputs(m_connection.get());
771 checkErrors();
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);
779 checkErrors();
782 void Connection::DisableOutput(int id)
784 prechecksNoCommandsList();
785 mpd_run_disable_output(m_connection.get(), id);
786 checkErrors();
789 StringIterator Connection::GetURLHandlers()
791 prechecksNoCommandsList();
792 mpd_send_list_url_schemes(m_connection.get());
793 checkErrors();
794 return StringIterator(m_connection.get(), [](StringIterator::State &state) {
795 auto src = mpd_recv_pair_named(state.connection(), "handler");
796 if (src != nullptr)
798 state.setObject(src->value);
799 mpd_return_pair(state.connection(), src);
800 return true;
802 else
803 return false;
807 StringIterator Connection::GetTagTypes()
810 prechecksNoCommandsList();
811 mpd_send_list_tag_types(m_connection.get());
812 checkErrors();
813 return StringIterator(m_connection.get(), [](StringIterator::State &state) {
814 auto src = mpd_recv_pair_named(state.connection(), "tagtype");
815 if (src != nullptr)
817 state.setObject(src->value);
818 mpd_return_pair(state.connection(), src);
819 return true;
821 else
822 return false;
826 void Connection::checkConnection() const
828 if (!m_connection)
829 throw ClientError(MPD_ERROR_STATE, "No active MPD connection", false);
832 void Connection::prechecks()
834 checkConnection();
835 noidle();
838 void Connection::prechecksNoCommandsList()
840 assert(!m_command_list_active);
841 prechecks();
844 void Connection::checkErrors() const
846 checkConnectionErrors(m_connection.get());