1 /***************************************************************************
2  *   Copyright (C) 2008-2021 by Andrzej Rybczak                            *
3  *   andrzej@rybczak.net                                                   *
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  ***************************************************************************/
20 
21 #include <cassert>
22 #include <cstdlib>
23 #include <algorithm>
24 #include <map>
25 #include <boost/regex.hpp>
26 
27 #include "charset.h"
28 #include "mpdpp.h"
29 
30 MPD::Connection Mpd;
31 
32 namespace {
33 
mpdDirectory(const std::string & directory)34 const char *mpdDirectory(const std::string &directory)
35 {
36 	// MPD <= 0.19 accepts "/" for a root directory whereas later
37 	// versions do not, so provide a compatibility layer.
38 	if (directory == "/")
39 		return "";
40 	else
41 		return directory.c_str();
42 }
43 
44 template <typename ObjectT, typename SourceT>
45 std::function<bool(typename MPD::Iterator<ObjectT>::State &)>
defaultFetcher(SourceT * (fetcher)(mpd_connection *))46 defaultFetcher(SourceT *(fetcher)(mpd_connection *))
47 {
48 	return [fetcher](typename MPD::Iterator<ObjectT>::State &state) {
49 		auto src = fetcher(state.connection());
50 		if (src != nullptr)
51 		{
52 			state.setObject(src);
53 			return true;
54 		}
55 		else
56 			return false;
57 	};
58 }
59 
fetchItemSong(MPD::SongIterator::State & state)60 bool fetchItemSong(MPD::SongIterator::State &state)
61 {
62 	auto src = mpd_recv_entity(state.connection());
63 	while (src != nullptr && mpd_entity_get_type(src) != MPD_ENTITY_TYPE_SONG)
64 	{
65 		mpd_entity_free(src);
66 		src = mpd_recv_entity(state.connection());
67 	}
68 	if (src != nullptr)
69 	{
70 		state.setObject(mpd_song_dup(mpd_entity_get_song(src)));
71 		mpd_entity_free(src);
72 		return true;
73 	}
74 	else
75 		return false;
76 }
77 
78 }
79 
80 namespace MPD {
81 
checkConnectionErrors(mpd_connection * conn)82 void checkConnectionErrors(mpd_connection *conn)
83 {
84 	mpd_error code = mpd_connection_get_error(conn);
85 	if (code != MPD_ERROR_SUCCESS)
86 	{
87 		std::string msg = mpd_connection_get_error_message(conn);
88 		if (code == MPD_ERROR_SERVER)
89 		{
90 			mpd_server_error server_code = mpd_connection_get_server_error(conn);
91 			bool clearable = mpd_connection_clear_error(conn);
92 			throw ServerError(server_code, msg, clearable);
93 		}
94 		else
95 		{
96 			bool clearable = mpd_connection_clear_error(conn);
97 			throw ClientError(code, msg, clearable);
98 		}
99 	}
100 }
101 
Connection()102 Connection::Connection() : m_connection(nullptr),
103 				m_command_list_active(false),
104 				m_idle(false),
105 				m_host("localhost"),
106 				m_port(6600),
107 				m_timeout(15)
108 {
109 }
110 
Connect()111 void Connection::Connect()
112 {
113 	assert(!m_connection);
114 	try
115 	{
116 		m_connection.reset(mpd_connection_new(m_host.c_str(), m_port, m_timeout * 1000));
117 		checkErrors();
118 		if (!m_password.empty())
119 			SendPassword();
120 		m_fd = mpd_connection_get_fd(m_connection.get());
121 		checkErrors();
122 	}
123 	catch (MPD::ClientError &e)
124 	{
125 		Disconnect();
126 		throw e;
127 	}
128 }
129 
Connected() const130 bool Connection::Connected() const
131 {
132 	return m_connection.get() != nullptr;
133 }
134 
Disconnect()135 void Connection::Disconnect()
136 {
137 	m_connection = nullptr;
138 	m_command_list_active = false;
139 	m_idle = false;
140 }
141 
Version() const142 unsigned Connection::Version() const
143 {
144 	return m_connection ? mpd_connection_get_server_version(m_connection.get())[1] : 0;
145 }
146 
SetHostname(const std::string & host)147 void Connection::SetHostname(const std::string &host)
148 {
149 	size_t at = host.find("@");
150 	if (at != std::string::npos)
151 	{
152 		m_password = host.substr(0, at);
153 		m_host = host.substr(at+1);
154 	}
155 	else
156 		m_host = host;
157 }
158 
SendPassword()159 void Connection::SendPassword()
160 {
161 	assert(m_connection);
162 	noidle();
163 	assert(!m_command_list_active);
164 	mpd_run_password(m_connection.get(), m_password.c_str());
165 	checkErrors();
166 }
167 
idle()168 void Connection::idle()
169 {
170 	checkConnection();
171 	if (!m_idle)
172 	{
173 		mpd_send_idle(m_connection.get());
174 		checkErrors();
175 	}
176 	m_idle = true;
177 }
178 
noidle()179 int Connection::noidle()
180 {
181 	checkConnection();
182 	int flags = 0;
183 	if (m_idle && mpd_send_noidle(m_connection.get()))
184 	{
185 		m_idle = false;
186 		flags = mpd_recv_idle(m_connection.get(), true);
187 		mpd_response_finish(m_connection.get());
188 		checkErrors();
189 	}
190 	return flags;
191 }
192 
setNoidleCallback(NoidleCallback callback)193 void Connection::setNoidleCallback(NoidleCallback callback)
194 {
195 	m_noidle_callback = std::move(callback);
196 }
197 
getStatistics()198 Statistics Connection::getStatistics()
199 {
200 	prechecks();
201 	mpd_stats *stats = mpd_run_stats(m_connection.get());
202 	checkErrors();
203 	return Statistics(stats);
204 }
205 
getStatus()206 Status Connection::getStatus()
207 {
208 	prechecks();
209 	mpd_status *status = mpd_run_status(m_connection.get());
210 	checkErrors();
211 	return Status(status);
212 }
213 
UpdateDirectory(const std::string & path)214 void Connection::UpdateDirectory(const std::string &path)
215 {
216 	prechecksNoCommandsList();
217 	// Use update as mpd_run_update doesn't call mpd_response_finish if the id
218 	// returned from mpd_recv_update_id is 0 which breaks mopidy.
219 	mpd_send_update(m_connection.get(), path.c_str());
220 	mpd_recv_update_id(m_connection.get());
221 	mpd_response_finish(m_connection.get());
222 	checkErrors();
223 }
224 
Play()225 void Connection::Play()
226 {
227 	prechecksNoCommandsList();
228 	mpd_run_play(m_connection.get());
229 	checkErrors();
230 }
231 
Play(int pos)232 void Connection::Play(int pos)
233 {
234 	prechecksNoCommandsList();
235 	mpd_run_play_pos(m_connection.get(), pos);
236 	checkErrors();
237 }
238 
PlayID(int id)239 void Connection::PlayID(int id)
240 {
241 	prechecksNoCommandsList();
242 	mpd_run_play_id(m_connection.get(), id);
243 	checkErrors();
244 }
245 
Pause(bool state)246 void Connection::Pause(bool state)
247 {
248 	prechecksNoCommandsList();
249 	mpd_run_pause(m_connection.get(), state);
250 	checkErrors();
251 }
252 
Toggle()253 void Connection::Toggle()
254 {
255 	prechecksNoCommandsList();
256 	mpd_run_toggle_pause(m_connection.get());
257 	checkErrors();
258 }
259 
Stop()260 void Connection::Stop()
261 {
262 	prechecksNoCommandsList();
263 	mpd_run_stop(m_connection.get());
264 	checkErrors();
265 }
266 
Next()267 void Connection::Next()
268 {
269 	prechecksNoCommandsList();
270 	mpd_run_next(m_connection.get());
271 	checkErrors();
272 }
273 
Prev()274 void Connection::Prev()
275 {
276 	prechecksNoCommandsList();
277 	mpd_run_previous(m_connection.get());
278 	checkErrors();
279 }
280 
Move(unsigned from,unsigned to)281 void Connection::Move(unsigned from, unsigned to)
282 {
283 	prechecks();
284 	if (m_command_list_active)
285 		mpd_send_move(m_connection.get(), from, to);
286 	else
287 	{
288 		mpd_run_move(m_connection.get(), from, to);
289 		checkErrors();
290 	}
291 }
292 
Swap(unsigned from,unsigned to)293 void Connection::Swap(unsigned from, unsigned to)
294 {
295 	prechecks();
296 	if (m_command_list_active)
297 		mpd_send_swap(m_connection.get(), from, to);
298 	else
299 	{
300 		mpd_run_swap(m_connection.get(), from, to);
301 		checkErrors();
302 	}
303 }
304 
Seek(unsigned pos,unsigned where)305 void Connection::Seek(unsigned pos, unsigned where)
306 {
307 	prechecksNoCommandsList();
308 	mpd_run_seek_pos(m_connection.get(), pos, where);
309 	checkErrors();
310 }
311 
Shuffle()312 void Connection::Shuffle()
313 {
314 	prechecksNoCommandsList();
315 	mpd_run_shuffle(m_connection.get());
316 	checkErrors();
317 }
318 
ShuffleRange(unsigned start,unsigned end)319 void Connection::ShuffleRange(unsigned start, unsigned end)
320 {
321 	prechecksNoCommandsList();
322 	mpd_run_shuffle_range(m_connection.get(), start, end);
323 	checkErrors();
324 }
325 
ClearMainPlaylist()326 void Connection::ClearMainPlaylist()
327 {
328 	prechecksNoCommandsList();
329 	mpd_run_clear(m_connection.get());
330 	checkErrors();
331 }
332 
ClearPlaylist(const std::string & playlist)333 void Connection::ClearPlaylist(const std::string &playlist)
334 {
335 	prechecksNoCommandsList();
336 	mpd_run_playlist_clear(m_connection.get(), playlist.c_str());
337 	checkErrors();
338 }
339 
AddToPlaylist(const std::string & path,const Song & s)340 void Connection::AddToPlaylist(const std::string &path, const Song &s)
341 {
342 	AddToPlaylist(path, s.getURI());
343 }
344 
AddToPlaylist(const std::string & path,const std::string & file)345 void Connection::AddToPlaylist(const std::string &path, const std::string &file)
346 {
347 	prechecks();
348 	if (m_command_list_active)
349 		mpd_send_playlist_add(m_connection.get(), path.c_str(), file.c_str());
350 	else
351 	{
352 		mpd_run_playlist_add(m_connection.get(), path.c_str(), file.c_str());
353 		checkErrors();
354 	}
355 }
356 
PlaylistMove(const std::string & path,int from,int to)357 void Connection::PlaylistMove(const std::string &path, int from, int to)
358 {
359 	prechecks();
360 	if (m_command_list_active)
361 		mpd_send_playlist_move(m_connection.get(), path.c_str(), from, to);
362 	else
363 	{
364 		mpd_send_playlist_move(m_connection.get(), path.c_str(), from, to);
365 		mpd_response_finish(m_connection.get());
366 		checkErrors();
367 	}
368 }
369 
Rename(const std::string & from,const std::string & to)370 void Connection::Rename(const std::string &from, const std::string &to)
371 {
372 	prechecksNoCommandsList();
373 	mpd_run_rename(m_connection.get(), from.c_str(), to.c_str());
374 	checkErrors();
375 }
376 
GetPlaylistChanges(unsigned version)377 SongIterator Connection::GetPlaylistChanges(unsigned version)
378 {
379 	prechecksNoCommandsList();
380 	mpd_send_queue_changes_meta(m_connection.get(), version);
381 	checkErrors();
382 	return SongIterator(m_connection.get(), defaultFetcher<Song>(mpd_recv_song));
383 }
384 
GetCurrentSong()385 Song Connection::GetCurrentSong()
386 {
387 	prechecksNoCommandsList();
388 	mpd_send_current_song(m_connection.get());
389 	mpd_song *s = mpd_recv_song(m_connection.get());
390 	mpd_response_finish(m_connection.get());
391 	checkErrors();
392 	// currentsong doesn't return error if there is no playing song.
393 	if (s == nullptr)
394 		return Song();
395 	else
396 		return Song(s);
397 }
398 
GetSong(const std::string & path)399 Song Connection::GetSong(const std::string &path)
400 {
401 	prechecksNoCommandsList();
402 	mpd_send_list_all_meta(m_connection.get(), path.c_str());
403 	mpd_song *s = mpd_recv_song(m_connection.get());
404 	mpd_response_finish(m_connection.get());
405 	checkErrors();
406 	return Song(s);
407 }
408 
GetPlaylistContent(const std::string & path)409 SongIterator Connection::GetPlaylistContent(const std::string &path)
410 {
411 	prechecksNoCommandsList();
412 	mpd_send_list_playlist_meta(m_connection.get(), path.c_str());
413 	SongIterator result(m_connection.get(), defaultFetcher<Song>(mpd_recv_song));
414 	checkErrors();
415 	return result;
416 }
417 
GetPlaylistContentNoInfo(const std::string & path)418 SongIterator Connection::GetPlaylistContentNoInfo(const std::string &path)
419 {
420 	prechecksNoCommandsList();
421 	mpd_send_list_playlist(m_connection.get(), path.c_str());
422 	SongIterator result(m_connection.get(), defaultFetcher<Song>(mpd_recv_song));
423 	checkErrors();
424 	return result;
425 }
426 
GetSupportedExtensions()427 StringIterator Connection::GetSupportedExtensions()
428 {
429 	prechecksNoCommandsList();
430 	mpd_send_command(m_connection.get(), "decoders", NULL);
431 	checkErrors();
432 	return StringIterator(m_connection.get(), [](StringIterator::State &state) {
433 		auto src = mpd_recv_pair_named(state.connection(), "suffix");
434 		if (src != nullptr)
435 		{
436 			state.setObject(src->value);
437 			mpd_return_pair(state.connection(), src);
438 			return true;
439 		}
440 		else
441 			return false;
442 	});
443 }
444 
SetRepeat(bool mode)445 void Connection::SetRepeat(bool mode)
446 {
447 	prechecksNoCommandsList();
448 	mpd_run_repeat(m_connection.get(), mode);
449 	checkErrors();
450 }
451 
SetRandom(bool mode)452 void Connection::SetRandom(bool mode)
453 {
454 	prechecksNoCommandsList();
455 	mpd_run_random(m_connection.get(), mode);
456 	checkErrors();
457 }
458 
SetSingle(bool mode)459 void Connection::SetSingle(bool mode)
460 {
461 	prechecksNoCommandsList();
462 	mpd_run_single(m_connection.get(), mode);
463 	checkErrors();
464 }
465 
SetConsume(bool mode)466 void Connection::SetConsume(bool mode)
467 {
468 	prechecksNoCommandsList();
469 	mpd_run_consume(m_connection.get(), mode);
470 	checkErrors();
471 }
472 
SetVolume(unsigned vol)473 void Connection::SetVolume(unsigned vol)
474 {
475 	prechecksNoCommandsList();
476 	mpd_run_set_volume(m_connection.get(), vol);
477 	checkErrors();
478 }
479 
ChangeVolume(int change)480 void Connection::ChangeVolume(int change)
481 {
482 	prechecksNoCommandsList();
483 	mpd_run_change_volume(m_connection.get(), change);
484 	checkErrors();
485 }
486 
487 
GetReplayGainMode()488 std::string Connection::GetReplayGainMode()
489 {
490 	prechecksNoCommandsList();
491 	mpd_send_command(m_connection.get(), "replay_gain_status", NULL);
492 	std::string result;
493 	if (mpd_pair *pair = mpd_recv_pair_named(m_connection.get(), "replay_gain_mode"))
494 	{
495 		result = pair->value;
496 		mpd_return_pair(m_connection.get(), pair);
497 	}
498 	mpd_response_finish(m_connection.get());
499 	checkErrors();
500 	return result;
501 }
502 
SetReplayGainMode(ReplayGainMode mode)503 void Connection::SetReplayGainMode(ReplayGainMode mode)
504 {
505 	prechecksNoCommandsList();
506 	const char *rg_mode;
507 	switch (mode)
508 	{
509 		case rgmOff:
510 			rg_mode = "off";
511 			break;
512 		case rgmTrack:
513 			rg_mode = "track";
514 			break;
515 		case rgmAlbum:
516 			rg_mode = "album";
517 			break;
518 		default:
519 			rg_mode = "";
520 			break;
521 	}
522 	mpd_send_command(m_connection.get(), "replay_gain_mode", rg_mode, NULL);
523 	mpd_response_finish(m_connection.get());
524 	checkErrors();
525 }
526 
SetCrossfade(unsigned crossfade)527 void Connection::SetCrossfade(unsigned crossfade)
528 {
529 	prechecksNoCommandsList();
530 	mpd_run_crossfade(m_connection.get(), crossfade);
531 	checkErrors();
532 }
533 
SetPriority(const Song & s,int prio)534 void Connection::SetPriority(const Song &s, int prio)
535 {
536 	prechecks();
537 	if (m_command_list_active)
538 		mpd_send_prio_id(m_connection.get(), prio, s.getID());
539 	else
540 	{
541 		mpd_run_prio_id(m_connection.get(), prio, s.getID());
542 		checkErrors();
543 	}
544 }
545 
AddSong(const std::string & path,int pos)546 int Connection::AddSong(const std::string &path, int pos)
547 {
548 	prechecks();
549 	int id;
550 	if (pos < 0)
551 		mpd_send_add_id(m_connection.get(), path.c_str());
552 	else
553 		mpd_send_add_id_to(m_connection.get(), path.c_str(), pos);
554 	if (!m_command_list_active)
555 	{
556 		id = mpd_recv_song_id(m_connection.get());
557 		mpd_response_finish(m_connection.get());
558 		checkErrors();
559 	}
560 	else
561 		id = 0;
562 	return id;
563 }
564 
AddSong(const Song & s,int pos)565 int Connection::AddSong(const Song &s, int pos)
566 {
567 	return AddSong((!s.isFromDatabase() ? "file://" : "") + s.getURI(), pos);
568 }
569 
Add(const std::string & path)570 bool Connection::Add(const std::string &path)
571 {
572 	bool result;
573 	prechecks();
574 	if (m_command_list_active)
575 		result = mpd_send_add(m_connection.get(), path.c_str());
576 	else
577 	{
578 		result = mpd_run_add(m_connection.get(), path.c_str());
579 		checkErrors();
580 	}
581 	return result;
582 }
583 
AddRandomTag(mpd_tag_type tag,size_t number,std::mt19937 & rng)584 bool Connection::AddRandomTag(mpd_tag_type tag, size_t number, std::mt19937 &rng)
585 {
586 	std::vector<std::string> tags(
587 		std::make_move_iterator(GetList(tag)),
588 		std::make_move_iterator(StringIterator())
589 	);
590 	if (number > tags.size())
591 		return false;
592 
593 	std::shuffle(tags.begin(), tags.end(), rng);
594 	auto it = tags.begin();
595 	for (size_t i = 0; i < number && it != tags.end(); ++i)
596 	{
597 		StartSearch(true);
598 		AddSearch(tag, *it++);
599 		std::vector<std::string> paths;
600 		MPD::SongIterator s = CommitSearchSongs(), end;
601 		for (; s != end; ++s)
602 			paths.push_back(s->getURI());
603 		StartCommandsList();
604 		for (const auto &path : paths)
605 			AddSong(path);
606 		CommitCommandsList();
607 	}
608 	return true;
609 }
610 
AddRandomSongs(size_t number,const std::string & random_exclude_pattern,std::mt19937 & rng)611 bool Connection::AddRandomSongs(size_t number, const std::string &random_exclude_pattern, std::mt19937 &rng)
612 {
613 	prechecksNoCommandsList();
614 	std::vector<std::string> files;
615 	mpd_send_list_all(m_connection.get(), "/");
616 	while (mpd_pair *item = mpd_recv_pair_named(m_connection.get(), "file"))
617 	{
618 		files.push_back(item->value);
619 		mpd_return_pair(m_connection.get(), item);
620 	}
621 	mpd_response_finish(m_connection.get());
622 	checkErrors();
623 
624 	if (number > files.size())
625 	{
626 		//if (itsErrorHandler)
627 		//	itsErrorHandler(this, 0, "Requested number of random songs is bigger than size of your library", itsErrorHandlerUserdata);
628 		return false;
629 	}
630 	else
631 	{
632 		std::shuffle(files.begin(), files.end(), rng);
633 		StartCommandsList();
634 		auto it = files.begin();
635 		boost::regex re(random_exclude_pattern);
636 		for (size_t i = 0; i < number && it != files.end(); ++it) {
637 			if (random_exclude_pattern.empty() || !boost::regex_match((*it), re)) {
638 				AddSong(*it);
639 				i++;
640 			}
641 		}
642 		CommitCommandsList();
643 	}
644 	return true;
645 }
646 
Delete(unsigned pos)647 void Connection::Delete(unsigned pos)
648 {
649 	prechecks();
650 	mpd_send_delete(m_connection.get(), pos);
651 	if (!m_command_list_active)
652 	{
653 		mpd_response_finish(m_connection.get());
654 		checkErrors();
655 	}
656 }
657 
DeleteRange(unsigned begin,unsigned end)658 void Connection::DeleteRange(unsigned begin, unsigned end)
659 {
660 	prechecks();
661 	mpd_send_delete_range(m_connection.get(), begin, end);
662 	if (!m_command_list_active)
663 	{
664 		mpd_response_finish(m_connection.get());
665 		checkErrors();
666 	}
667 }
668 
PlaylistDelete(const std::string & playlist,unsigned pos)669 void Connection::PlaylistDelete(const std::string &playlist, unsigned pos)
670 {
671 	prechecks();
672 	mpd_send_playlist_delete(m_connection.get(), playlist.c_str(), pos);
673 	if (!m_command_list_active)
674 	{
675 		mpd_response_finish(m_connection.get());
676 		checkErrors();
677 	}
678 }
679 
StartCommandsList()680 void Connection::StartCommandsList()
681 {
682 	prechecksNoCommandsList();
683 	mpd_command_list_begin(m_connection.get(), true);
684 	m_command_list_active = true;
685 	checkErrors();
686 }
687 
CommitCommandsList()688 void Connection::CommitCommandsList()
689 {
690 	prechecks();
691 	assert(m_command_list_active);
692 	mpd_command_list_end(m_connection.get());
693 	mpd_response_finish(m_connection.get());
694 	m_command_list_active = false;
695 	checkErrors();
696 }
697 
DeletePlaylist(const std::string & name)698 void Connection::DeletePlaylist(const std::string &name)
699 {
700 	prechecksNoCommandsList();
701 	mpd_run_rm(m_connection.get(), name.c_str());
702 	checkErrors();
703 }
704 
LoadPlaylist(const std::string & name)705 bool Connection::LoadPlaylist(const std::string &name)
706 {
707 	prechecksNoCommandsList();
708 	bool result = mpd_run_load(m_connection.get(), name.c_str());
709 	checkErrors();
710 	return result;
711 }
712 
SavePlaylist(const std::string & name)713 void Connection::SavePlaylist(const std::string &name)
714 {
715 	prechecksNoCommandsList();
716 	mpd_send_save(m_connection.get(), name.c_str());
717 	mpd_response_finish(m_connection.get());
718 	checkErrors();
719 }
720 
GetPlaylists()721 PlaylistIterator Connection::GetPlaylists()
722 {
723 	prechecksNoCommandsList();
724 	mpd_send_list_playlists(m_connection.get());
725 	checkErrors();
726 	return PlaylistIterator(m_connection.get(), defaultFetcher<Playlist>(mpd_recv_playlist));
727 }
728 
GetList(mpd_tag_type type)729 StringIterator Connection::GetList(mpd_tag_type type)
730 {
731 	prechecksNoCommandsList();
732 	mpd_search_db_tags(m_connection.get(), type);
733 	mpd_search_commit(m_connection.get());
734 	checkErrors();
735 	return StringIterator(m_connection.get(), [type](StringIterator::State &state) {
736 		auto src = mpd_recv_pair_tag(state.connection(), type);
737 		if (src != nullptr)
738 		{
739 			state.setObject(src->value);
740 			mpd_return_pair(state.connection(), src);
741 			return true;
742 		}
743 		else
744 			return false;
745 	});
746 }
747 
StartSearch(bool exact_match)748 void Connection::StartSearch(bool exact_match)
749 {
750 	prechecksNoCommandsList();
751 	mpd_search_db_songs(m_connection.get(), exact_match);
752 }
753 
StartFieldSearch(mpd_tag_type item)754 void Connection::StartFieldSearch(mpd_tag_type item)
755 {
756 	prechecksNoCommandsList();
757 	mpd_search_db_tags(m_connection.get(), item);
758 }
759 
AddSearch(mpd_tag_type item,const std::string & str) const760 void Connection::AddSearch(mpd_tag_type item, const std::string &str) const
761 {
762 	checkConnection();
763 	mpd_search_add_tag_constraint(m_connection.get(), MPD_OPERATOR_DEFAULT, item, str.c_str());
764 }
765 
AddSearchAny(const std::string & str) const766 void Connection::AddSearchAny(const std::string &str) const
767 {
768 	checkConnection();
769 	mpd_search_add_any_tag_constraint(m_connection.get(), MPD_OPERATOR_DEFAULT, str.c_str());
770 }
771 
AddSearchURI(const std::string & str) const772 void Connection::AddSearchURI(const std::string &str) const
773 {
774 	checkConnection();
775 	mpd_search_add_uri_constraint(m_connection.get(), MPD_OPERATOR_DEFAULT, str.c_str());
776 }
777 
CommitSearchSongs()778 SongIterator Connection::CommitSearchSongs()
779 {
780 	prechecksNoCommandsList();
781 	mpd_search_commit(m_connection.get());
782 	checkErrors();
783 	return SongIterator(m_connection.get(), defaultFetcher<Song>(mpd_recv_song));
784 }
785 
GetDirectory(const std::string & directory)786 ItemIterator Connection::GetDirectory(const std::string &directory)
787 {
788 	prechecksNoCommandsList();
789 	mpd_send_list_meta(m_connection.get(), mpdDirectory(directory));
790 	checkErrors();
791 	return ItemIterator(m_connection.get(), defaultFetcher<Item>(mpd_recv_entity));
792 }
793 
GetDirectoryRecursive(const std::string & directory)794 SongIterator Connection::GetDirectoryRecursive(const std::string &directory)
795 {
796 	prechecksNoCommandsList();
797 	mpd_send_list_all_meta(m_connection.get(), mpdDirectory(directory));
798 	checkErrors();
799 	return SongIterator(m_connection.get(), fetchItemSong);
800 }
801 
GetDirectories(const std::string & directory)802 DirectoryIterator Connection::GetDirectories(const std::string &directory)
803 {
804 	prechecksNoCommandsList();
805 	mpd_send_list_meta(m_connection.get(), mpdDirectory(directory));
806 	checkErrors();
807 	return DirectoryIterator(m_connection.get(), defaultFetcher<Directory>(mpd_recv_directory));
808 }
809 
GetSongs(const std::string & directory)810 SongIterator Connection::GetSongs(const std::string &directory)
811 {
812 	prechecksNoCommandsList();
813 	mpd_send_list_meta(m_connection.get(), mpdDirectory(directory));
814 	checkErrors();
815 	return SongIterator(m_connection.get(), defaultFetcher<Song>(mpd_recv_song));
816 }
817 
GetOutputs()818 OutputIterator Connection::GetOutputs()
819 {
820 	prechecksNoCommandsList();
821 	mpd_send_outputs(m_connection.get());
822 	checkErrors();
823 	return OutputIterator(m_connection.get(), defaultFetcher<Output>(mpd_recv_output));
824 }
825 
EnableOutput(int id)826 void Connection::EnableOutput(int id)
827 {
828 	prechecksNoCommandsList();
829 	mpd_run_enable_output(m_connection.get(), id);
830 	checkErrors();
831 }
832 
DisableOutput(int id)833 void Connection::DisableOutput(int id)
834 {
835 	prechecksNoCommandsList();
836 	mpd_run_disable_output(m_connection.get(), id);
837 	checkErrors();
838 }
839 
GetURLHandlers()840 StringIterator Connection::GetURLHandlers()
841 {
842 	prechecksNoCommandsList();
843 	mpd_send_list_url_schemes(m_connection.get());
844 	checkErrors();
845 	return StringIterator(m_connection.get(), [](StringIterator::State &state) {
846 		auto src = mpd_recv_pair_named(state.connection(), "handler");
847 		if (src != nullptr)
848 		{
849 			state.setObject(src->value);
850 			mpd_return_pair(state.connection(), src);
851 			return true;
852 		}
853 		else
854 			return false;
855 	});
856 }
857 
GetTagTypes()858 StringIterator Connection::GetTagTypes()
859 {
860 
861 	prechecksNoCommandsList();
862 	mpd_send_list_tag_types(m_connection.get());
863 	checkErrors();
864 	return StringIterator(m_connection.get(), [](StringIterator::State &state) {
865 		auto src = mpd_recv_pair_named(state.connection(), "tagtype");
866 		if (src != nullptr)
867 		{
868 			state.setObject(src->value);
869 			mpd_return_pair(state.connection(), src);
870 			return true;
871 		}
872 		else
873 			return false;
874 	});
875 }
876 
checkConnection() const877 void Connection::checkConnection() const
878 {
879 	if (!m_connection)
880 		throw ClientError(MPD_ERROR_STATE, "No active MPD connection", false);
881 }
882 
prechecks()883 void Connection::prechecks()
884 {
885 	checkConnection();
886 	int flags = noidle();
887 	if (flags && m_noidle_callback)
888 		m_noidle_callback(flags);
889 }
890 
prechecksNoCommandsList()891 void Connection::prechecksNoCommandsList()
892 {
893 	assert(!m_command_list_active);
894 	prechecks();
895 }
896 
checkErrors() const897 void Connection::checkErrors() const
898 {
899 	checkConnectionErrors(m_connection.get());
900 }
901 
902 }
903