set SIGWINCH handler before initializing ncurses to avoid races
[ncmpcpp.git] / src / mpdpp.cpp
blobb715e0f38c46b58bbf6f4e1e53c6aa733b2c31a2
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 "error.h"
28 #include "mpdpp.h"
30 MPD::Connection Mpd;
32 namespace MPD {//
34 Connection::Connection() : m_connection(nullptr),
35 m_command_list_active(false),
36 m_idle(false),
37 m_host("localhost"),
38 m_port(6600),
39 m_timeout(15)
43 Connection::~Connection()
45 if (m_connection)
46 mpd_connection_free(m_connection);
49 void Connection::Connect()
51 assert(!m_connection);
52 try
54 m_connection = mpd_connection_new(m_host.c_str(), m_port, m_timeout * 1000);
55 checkErrors();
56 if (!m_password.empty())
57 SendPassword();
58 m_fd = mpd_connection_get_fd(m_connection);
59 checkErrors();
61 catch (MPD::ClientError &e)
63 Disconnect();
64 throw e;
68 bool Connection::Connected() const
70 return m_connection;
73 void Connection::Disconnect()
75 if (m_connection)
76 mpd_connection_free(m_connection);
77 m_connection = nullptr;
78 m_command_list_active = false;
79 m_idle = false;
82 unsigned Connection::Version() const
84 return m_connection ? mpd_connection_get_server_version(m_connection)[1] : 0;
87 void Connection::SetHostname(const std::string &host)
89 size_t at = host.find("@");
90 if (at != std::string::npos)
92 m_password = host.substr(0, at);
93 m_host = host.substr(at+1);
95 else
96 m_host = host;
99 void Connection::SendPassword()
101 assert(m_connection);
102 noidle();
103 assert(!m_command_list_active);
104 mpd_run_password(m_connection, m_password.c_str());
105 checkErrors();
108 void Connection::idle()
110 checkConnection();
111 if (!m_idle)
113 mpd_send_idle(m_connection);
114 checkErrors();
116 m_idle = true;
119 int Connection::noidle()
121 checkConnection();
122 int flags = 0;
123 if (m_idle && mpd_send_noidle(m_connection))
125 m_idle = false;
126 flags = mpd_recv_idle(m_connection, true);
127 mpd_response_finish(m_connection);
128 checkErrors();
130 return flags;
133 Statistics Connection::getStatistics()
135 prechecks();
136 mpd_stats *stats = mpd_run_stats(m_connection);
137 checkErrors();
138 return Statistics(stats);
141 Status Connection::getStatus()
143 prechecks();
144 mpd_status *status = mpd_run_status(m_connection);
145 checkErrors();
146 return Status(status);
149 void Connection::UpdateDirectory(const std::string &path)
151 prechecksNoCommandsList();
152 mpd_run_update(m_connection, path.c_str());
153 checkErrors();
156 void Connection::Play()
158 prechecksNoCommandsList();
159 mpd_run_play(m_connection);
160 checkErrors();
163 void Connection::Play(int pos)
165 prechecksNoCommandsList();
166 mpd_run_play_pos(m_connection, pos);
167 checkErrors();
170 void Connection::PlayID(int id)
172 prechecksNoCommandsList();
173 mpd_run_play_id(m_connection, id);
174 checkErrors();
177 void Connection::Pause(bool state)
179 prechecksNoCommandsList();
180 mpd_run_pause(m_connection, state);
181 checkErrors();
184 void Connection::Toggle()
186 prechecksNoCommandsList();
187 mpd_run_toggle_pause(m_connection);
188 checkErrors();
191 void Connection::Stop()
193 prechecksNoCommandsList();
194 mpd_run_stop(m_connection);
195 checkErrors();
198 void Connection::Next()
200 prechecksNoCommandsList();
201 mpd_run_next(m_connection);
202 checkErrors();
205 void Connection::Prev()
207 prechecksNoCommandsList();
208 mpd_run_previous(m_connection);
209 checkErrors();
212 void Connection::Move(unsigned from, unsigned to)
214 prechecks();
215 if (m_command_list_active)
216 mpd_send_move(m_connection, from, to);
217 else
219 mpd_run_move(m_connection, from, to);
220 checkErrors();
224 void Connection::Swap(unsigned from, unsigned to)
226 prechecks();
227 if (m_command_list_active)
228 mpd_send_swap(m_connection, from, to);
229 else
231 mpd_run_swap(m_connection, from, to);
232 checkErrors();
236 void Connection::Seek(unsigned pos, unsigned where)
238 prechecksNoCommandsList();
239 mpd_run_seek_pos(m_connection, pos, where);
240 checkErrors();
243 void Connection::Shuffle()
245 prechecksNoCommandsList();
246 mpd_run_shuffle(m_connection);
247 checkErrors();
250 void Connection::ClearMainPlaylist()
252 prechecksNoCommandsList();
253 mpd_run_clear(m_connection);
254 checkErrors();
257 void Connection::ClearPlaylist(const std::string &playlist)
259 prechecksNoCommandsList();
260 mpd_run_playlist_clear(m_connection, playlist.c_str());
261 checkErrors();
264 void Connection::AddToPlaylist(const std::string &path, const Song &s)
266 AddToPlaylist(path, s.getURI());
269 void Connection::AddToPlaylist(const std::string &path, const std::string &file)
271 prechecks();
272 if (m_command_list_active)
273 mpd_send_playlist_add(m_connection, path.c_str(), file.c_str());
274 else
276 mpd_run_playlist_add(m_connection, path.c_str(), file.c_str());
277 checkErrors();
281 void Connection::PlaylistMove(const std::string &path, int from, int to)
283 prechecks();
284 if (m_command_list_active)
285 mpd_send_playlist_move(m_connection, path.c_str(), from, to);
286 else
288 mpd_send_playlist_move(m_connection, path.c_str(), from, to);
289 mpd_response_finish(m_connection);
290 checkErrors();
294 void Connection::Rename(const std::string &from, const std::string &to)
296 prechecksNoCommandsList();
297 mpd_run_rename(m_connection, from.c_str(), to.c_str());
298 checkErrors();
301 void Connection::GetPlaylistChanges(unsigned version, SongConsumer f)
303 prechecksNoCommandsList();
304 mpd_send_queue_changes_meta(m_connection, version);
305 while (mpd_song *s = mpd_recv_song(m_connection))
306 f(Song(s));
307 mpd_response_finish(m_connection);
308 checkErrors();
311 Song Connection::GetCurrentSong()
313 prechecksNoCommandsList();
314 mpd_send_current_song(m_connection);
315 mpd_song *s = mpd_recv_song(m_connection);
316 mpd_response_finish(m_connection);
317 checkErrors();
318 return Song(s);
321 Song Connection::GetSong(const std::string &path)
323 prechecksNoCommandsList();
324 mpd_send_list_all_meta(m_connection, path.c_str());
325 mpd_song *s = mpd_recv_song(m_connection);
326 mpd_response_finish(m_connection);
327 checkErrors();
328 return Song(s);
331 void Connection::GetPlaylistContent(const std::string &path, SongConsumer f)
333 prechecksNoCommandsList();
334 mpd_send_list_playlist_meta(m_connection, path.c_str());
335 while (mpd_song *s = mpd_recv_song(m_connection))
336 f(Song(s));
337 mpd_response_finish(m_connection);
338 checkErrors();
341 void Connection::GetPlaylistContentNoInfo(const std::string &path, SongConsumer f)
343 prechecksNoCommandsList();
344 mpd_send_list_playlist(m_connection, path.c_str());
345 while (mpd_song *s = mpd_recv_song(m_connection))
346 f(Song(s));
347 mpd_response_finish(m_connection);
348 checkErrors();
351 void Connection::GetSupportedExtensions(std::set<std::string> &acc)
353 prechecksNoCommandsList();
354 mpd_send_command(m_connection, "decoders", NULL);
355 while (mpd_pair *pair = mpd_recv_pair_named(m_connection, "suffix"))
357 acc.insert(pair->value);
358 mpd_return_pair(m_connection, pair);
360 mpd_response_finish(m_connection);
361 checkErrors();
364 void Connection::SetRepeat(bool mode)
366 prechecksNoCommandsList();
367 mpd_run_repeat(m_connection, mode);
368 checkErrors();
371 void Connection::SetRandom(bool mode)
373 prechecksNoCommandsList();
374 mpd_run_random(m_connection, mode);
375 checkErrors();
378 void Connection::SetSingle(bool mode)
380 prechecksNoCommandsList();
381 mpd_run_single(m_connection, mode);
382 checkErrors();
385 void Connection::SetConsume(bool mode)
387 prechecksNoCommandsList();
388 mpd_run_consume(m_connection, mode);
389 checkErrors();
392 void Connection::SetVolume(unsigned vol)
394 prechecksNoCommandsList();
395 mpd_run_set_volume(m_connection, vol);
396 checkErrors();
399 std::string Connection::GetReplayGainMode()
401 prechecksNoCommandsList();
402 mpd_send_command(m_connection, "replay_gain_status", NULL);
403 std::string result;
404 if (mpd_pair *pair = mpd_recv_pair_named(m_connection, "replay_gain_mode"))
406 result = pair->value;
407 mpd_return_pair(m_connection, pair);
409 mpd_response_finish(m_connection);
410 checkErrors();
411 return result;
414 void Connection::SetReplayGainMode(ReplayGainMode mode)
416 prechecksNoCommandsList();
417 const char *rg_mode;
418 switch (mode)
420 case rgmOff:
421 rg_mode = "off";
422 break;
423 case rgmTrack:
424 rg_mode = "track";
425 break;
426 case rgmAlbum:
427 rg_mode = "album";
428 break;
429 default:
430 rg_mode = "";
431 break;
433 mpd_send_command(m_connection, "replay_gain_mode", rg_mode, NULL);
434 mpd_response_finish(m_connection);
435 checkErrors();
438 void Connection::SetCrossfade(unsigned crossfade)
440 prechecksNoCommandsList();
441 mpd_run_crossfade(m_connection, crossfade);
442 checkErrors();
445 void Connection::SetPriority(const Song &s, int prio)
447 prechecks();
448 if (m_command_list_active)
449 mpd_send_prio_id(m_connection, prio, s.getID());
450 else
452 mpd_run_prio_id(m_connection, prio, s.getID());
453 checkErrors();
457 int Connection::AddSong(const std::string &path, int pos)
459 prechecks();
460 int id;
461 if (pos < 0)
462 mpd_send_add_id(m_connection, path.c_str());
463 else
464 mpd_send_add_id_to(m_connection, path.c_str(), pos);
465 if (!m_command_list_active)
467 id = mpd_recv_song_id(m_connection);
468 mpd_response_finish(m_connection);
469 checkErrors();
471 else
472 id = 0;
473 return id;
476 int Connection::AddSong(const Song &s, int pos)
478 return AddSong((!s.isFromDatabase() ? "file://" : "") + s.getURI(), pos);
481 void Connection::Add(const std::string &path)
483 prechecks();
484 if (m_command_list_active)
485 mpd_send_add(m_connection, path.c_str());
486 else
488 mpd_run_add(m_connection, path.c_str());
489 checkErrors();
493 bool Connection::AddRandomTag(mpd_tag_type tag, size_t number)
495 StringList tags;
496 GetList(tag, [&tags](std::string tag_name) {
497 tags.push_back(tag_name);
499 if (number > tags.size())
501 //if (itsErrorHandler)
502 // itsErrorHandler(this, 0, "Requested number is out of range", itsErrorHandlerUserdata);
503 return false;
505 else
507 std::random_shuffle(tags.begin(), tags.end());
508 auto it = tags.begin();
509 for (size_t i = 0; i < number && it != tags.end(); ++i)
511 StartSearch(1);
512 AddSearch(tag, *it++);
513 SongList songs;
514 CommitSearchSongs([&songs](MPD::Song s) {
515 songs.push_back(s);
517 StartCommandsList();
518 for (auto s = songs.begin(); s != songs.end(); ++s)
519 AddSong(*s);
520 CommitCommandsList();
523 return true;
526 bool Connection::AddRandomSongs(size_t number)
528 prechecksNoCommandsList();
529 StringList files;
530 mpd_send_list_all(m_connection, "/");
531 while (mpd_pair *item = mpd_recv_pair_named(m_connection, "file"))
533 files.push_back(item->value);
534 mpd_return_pair(m_connection, item);
536 mpd_response_finish(m_connection);
537 checkErrors();
539 if (number > files.size())
541 //if (itsErrorHandler)
542 // itsErrorHandler(this, 0, "Requested number of random songs is bigger than size of your library", itsErrorHandlerUserdata);
543 return false;
545 else
547 std::random_shuffle(files.begin(), files.end());
548 StartCommandsList();
549 auto it = files.begin();
550 for (size_t i = 0; i < number && it != files.end(); ++i, ++it)
551 AddSong(*it);
552 CommitCommandsList();
554 return true;
557 void Connection::Delete(unsigned pos)
559 prechecks();
560 mpd_send_delete(m_connection, pos);
561 if (!m_command_list_active)
563 mpd_response_finish(m_connection);
564 checkErrors();
568 void Connection::PlaylistDelete(const std::string &playlist, unsigned pos)
570 prechecks();
571 mpd_send_playlist_delete(m_connection, playlist.c_str(), pos);
572 if (!m_command_list_active)
574 mpd_response_finish(m_connection);
575 checkErrors();
579 void Connection::StartCommandsList()
581 prechecksNoCommandsList();
582 mpd_command_list_begin(m_connection, true);
583 m_command_list_active = true;
584 checkErrors();
587 void Connection::CommitCommandsList()
589 prechecks();
590 assert(m_command_list_active);
591 mpd_command_list_end(m_connection);
592 mpd_response_finish(m_connection);
593 m_command_list_active = false;
594 checkErrors();
597 void Connection::DeletePlaylist(const std::string &name)
599 prechecksNoCommandsList();
600 mpd_run_rm(m_connection, name.c_str());
601 checkErrors();
604 void Connection::LoadPlaylist(const std::string &name)
606 prechecksNoCommandsList();
607 mpd_run_load(m_connection, name.c_str());
608 checkErrors();
611 void Connection::SavePlaylist(const std::string &name)
613 prechecksNoCommandsList();
614 mpd_send_save(m_connection, name.c_str());
615 mpd_response_finish(m_connection);
616 checkErrors();
619 void Connection::GetPlaylists(StringConsumer f)
621 GetDirectory("/", [&f](Item &&item) {
622 if (item.type == itPlaylist)
623 f(std::move(item.name));
627 void Connection::GetList(mpd_tag_type type, StringConsumer f)
629 prechecksNoCommandsList();
630 mpd_search_db_tags(m_connection, type);
631 mpd_search_commit(m_connection);
632 while (mpd_pair *item = mpd_recv_pair_tag(m_connection, type))
634 f(std::string(item->value));
635 mpd_return_pair(m_connection, item);
637 mpd_response_finish(m_connection);
638 checkErrors();
641 void Connection::StartSearch(bool exact_match)
643 prechecksNoCommandsList();
644 mpd_search_db_songs(m_connection, exact_match);
647 void Connection::StartFieldSearch(mpd_tag_type item)
649 prechecksNoCommandsList();
650 mpd_search_db_tags(m_connection, item);
653 void Connection::AddSearch(mpd_tag_type item, const std::string &str) const
655 checkConnection();
656 mpd_search_add_tag_constraint(m_connection, MPD_OPERATOR_DEFAULT, item, str.c_str());
659 void Connection::AddSearchAny(const std::string &str) const
661 checkConnection();
662 mpd_search_add_any_tag_constraint(m_connection, MPD_OPERATOR_DEFAULT, str.c_str());
665 void Connection::AddSearchURI(const std::string &str) const
667 checkConnection();
668 mpd_search_add_uri_constraint(m_connection, MPD_OPERATOR_DEFAULT, str.c_str());
671 void Connection::CommitSearchSongs(SongConsumer f)
673 prechecksNoCommandsList();
674 mpd_search_commit(m_connection);
675 while (mpd_song *s = mpd_recv_song(m_connection))
676 f(Song(s));
677 mpd_response_finish(m_connection);
678 checkErrors();
681 void Connection::CommitSearchTags(StringConsumer f)
683 prechecksNoCommandsList();
684 mpd_search_commit(m_connection);
685 while (mpd_pair *tag = mpd_recv_pair_tag(m_connection, m_searched_field))
687 f(std::string(tag->value));
688 mpd_return_pair(m_connection, tag);
690 mpd_response_finish(m_connection);
691 checkErrors();
694 void Connection::GetDirectory(const std::string &directory, ItemConsumer f)
696 prechecksNoCommandsList();
697 mpd_send_list_meta(m_connection, directory.c_str());
698 while (mpd_entity *item = mpd_recv_entity(m_connection))
700 Item it;
701 switch (mpd_entity_get_type(item))
703 case MPD_ENTITY_TYPE_DIRECTORY:
704 it.name = mpd_directory_get_path(mpd_entity_get_directory(item));
705 it.type = itDirectory;
706 break;
707 case MPD_ENTITY_TYPE_SONG:
708 it.song = std::make_shared<Song>(Song(mpd_song_dup(mpd_entity_get_song(item))));
709 it.type = itSong;
710 break;
711 case MPD_ENTITY_TYPE_PLAYLIST:
712 it.name = mpd_playlist_get_path(mpd_entity_get_playlist(item));
713 it.type = itPlaylist;
714 break;
715 default:
716 assert(false);
718 mpd_entity_free(item);
719 f(std::move(it));
721 mpd_response_finish(m_connection);
722 checkErrors();
725 void Connection::GetDirectoryRecursive(const std::string &directory, SongConsumer f)
727 prechecksNoCommandsList();
728 mpd_send_list_all_meta(m_connection, directory.c_str());
729 while (mpd_entity *e = mpd_recv_entity(m_connection)) {
730 if (mpd_entity_get_type(e) == MPD_ENTITY_TYPE_SONG)
731 f(Song(mpd_song_dup(mpd_entity_get_song(e))));
732 mpd_entity_free(e);
734 mpd_response_finish(m_connection);
735 checkErrors();
738 void Connection::GetDirectories(const std::string &directory, StringConsumer f)
740 prechecksNoCommandsList();
741 mpd_send_list_meta(m_connection, directory.c_str());
742 while (mpd_directory *dir = mpd_recv_directory(m_connection))
744 f(std::string(mpd_directory_get_path(dir)));
745 mpd_directory_free(dir);
747 mpd_response_finish(m_connection);
748 checkErrors();
751 void Connection::GetSongs(const std::string &directory, SongConsumer f)
753 prechecksNoCommandsList();
754 mpd_send_list_meta(m_connection, directory.c_str());
755 while (mpd_song *s = mpd_recv_song(m_connection))
756 f(Song(s));
757 mpd_response_finish(m_connection);
758 checkErrors();
761 void Connection::GetOutputs(OutputConsumer f)
763 prechecksNoCommandsList();
764 mpd_send_outputs(m_connection);
765 while (mpd_output *output = mpd_recv_output(m_connection))
767 f(Output(mpd_output_get_name(output), mpd_output_get_enabled(output)));
768 mpd_output_free(output);
770 mpd_response_finish(m_connection);
771 checkErrors();
774 void Connection::EnableOutput(int id)
776 prechecksNoCommandsList();
777 mpd_run_enable_output(m_connection, id);
778 checkErrors();
781 void Connection::DisableOutput(int id)
783 prechecksNoCommandsList();
784 mpd_run_disable_output(m_connection, id);
785 checkErrors();
788 void Connection::GetURLHandlers(StringConsumer f)
790 prechecksNoCommandsList();
791 mpd_send_list_url_schemes(m_connection);
792 while (mpd_pair *handler = mpd_recv_pair_named(m_connection, "handler"))
794 f(std::string(handler->value));
795 mpd_return_pair(m_connection, handler);
797 mpd_response_finish(m_connection);
798 checkErrors();
801 void Connection::GetTagTypes(StringConsumer f)
804 prechecksNoCommandsList();
805 mpd_send_list_tag_types(m_connection);
806 while (mpd_pair *tag_type = mpd_recv_pair_named(m_connection, "tagtype"))
808 f(std::string(tag_type->value));
809 mpd_return_pair(m_connection, tag_type);
811 mpd_response_finish(m_connection);
812 checkErrors();
815 void Connection::checkConnection() const
817 if (!m_connection)
818 throw ClientError(MPD_ERROR_STATE, "No active MPD connection", false);
821 void Connection::prechecks()
823 checkConnection();
824 noidle();
827 void Connection::prechecksNoCommandsList()
829 assert(!m_command_list_active);
830 prechecks();
833 void Connection::checkErrors() const
835 mpd_error code = mpd_connection_get_error(m_connection);
836 if (code != MPD_ERROR_SUCCESS)
838 std::string msg = mpd_connection_get_error_message(m_connection);
839 if (code == MPD_ERROR_SERVER)
841 mpd_server_error server_code = mpd_connection_get_server_error(m_connection);
842 bool clearable = mpd_connection_clear_error(m_connection);
843 throw ServerError(server_code, msg, clearable);
845 else
847 bool clearable = mpd_connection_clear_error(m_connection);
848 throw ClientError(code, msg, clearable);