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 #ifndef NCMPCPP_MPDPP_H
22 #define NCMPCPP_MPDPP_H
23 
24 #include <cassert>
25 #include <exception>
26 #include <random>
27 #include <set>
28 #include <stdexcept>
29 #include <vector>
30 
31 #include <mpd/client.h>
32 #include "song.h"
33 
34 namespace MPD {
35 
36 void checkConnectionErrors(mpd_connection *conn);
37 
38 enum PlayerState { psUnknown, psStop, psPlay, psPause };
39 enum ReplayGainMode { rgmOff, rgmTrack, rgmAlbum };
40 
41 struct Error: public std::exception
42 {
ErrorError43 	Error(std::string msg, bool clearable_)
44 		: m_msg(msg), m_clearable(clearable_) { }
~ErrorError45 	virtual ~Error() noexcept { }
46 
whatError47 	virtual const char *what() const noexcept { return m_msg.c_str(); }
clearableError48 	bool clearable() const { return m_clearable; }
49 
50 private:
51 	std::string m_msg;
52 	bool m_clearable;
53 };
54 
55 struct ClientError: public Error
56 {
ClientErrorClientError57 	ClientError(mpd_error code_, std::string msg, bool clearable_)
58 		: Error(msg, clearable_), m_code(code_) { }
~ClientErrorClientError59 	virtual ~ClientError() noexcept { }
60 
codeClientError61 	mpd_error code() const { return m_code; }
62 
63 private:
64 	mpd_error m_code;
65 };
66 
67 struct ServerError: public Error
68 {
ServerErrorServerError69 	ServerError(mpd_server_error code_, std::string msg, bool clearable_)
70 		: Error(msg, clearable_), m_code(code_) { }
~ServerErrorServerError71 	virtual ~ServerError() noexcept { }
72 
codeServerError73 	mpd_server_error code() const { return m_code; }
74 
75 private:
76 	mpd_server_error m_code;
77 };
78 
79 struct Statistics
80 {
81 	friend struct Connection;
82 
emptyStatistics83 	bool empty() const { return m_stats.get() == nullptr; }
84 
artistsStatistics85 	unsigned artists() const { return mpd_stats_get_number_of_artists(m_stats.get()); }
albumsStatistics86 	unsigned albums() const { return mpd_stats_get_number_of_albums(m_stats.get()); }
songsStatistics87 	unsigned songs() const { return mpd_stats_get_number_of_songs(m_stats.get()); }
playTimeStatistics88 	unsigned long playTime() const { return mpd_stats_get_play_time(m_stats.get()); }
uptimeStatistics89 	unsigned long uptime() const { return mpd_stats_get_uptime(m_stats.get()); }
dbUpdateTimeStatistics90 	unsigned long dbUpdateTime() const { return mpd_stats_get_db_update_time(m_stats.get()); }
dbPlayTimeStatistics91 	unsigned long dbPlayTime() const { return mpd_stats_get_db_play_time(m_stats.get()); }
92 
93 private:
StatisticsStatistics94 	Statistics(mpd_stats *stats) : m_stats(stats, mpd_stats_free) { }
95 
96 	std::shared_ptr<mpd_stats> m_stats;
97 };
98 
99 struct Status
100 {
101 	friend struct Connection;
102 
StatusStatus103 	Status() { }
104 
clearStatus105 	void clear() { m_status.reset(); }
emptyStatus106 	bool empty() const { return m_status.get() == nullptr; }
107 
volumeStatus108 	int volume() const { return mpd_status_get_volume(m_status.get()); }
repeatStatus109 	bool repeat() const { return mpd_status_get_repeat(m_status.get()); }
randomStatus110 	bool random() const { return mpd_status_get_random(m_status.get()); }
singleStatus111 	bool single() const { return mpd_status_get_single(m_status.get()); }
consumeStatus112 	bool consume() const { return mpd_status_get_consume(m_status.get()); }
playlistLengthStatus113 	unsigned playlistLength() const { return mpd_status_get_queue_length(m_status.get()); }
playlistVersionStatus114 	unsigned playlistVersion() const { return mpd_status_get_queue_version(m_status.get()); }
playerStateStatus115 	PlayerState playerState() const { return PlayerState(mpd_status_get_state(m_status.get())); }
crossfadeStatus116 	unsigned crossfade() const { return mpd_status_get_crossfade(m_status.get()); }
currentSongPositionStatus117 	int currentSongPosition() const { return mpd_status_get_song_pos(m_status.get()); }
currentSongIDStatus118 	int currentSongID() const { return mpd_status_get_song_id(m_status.get()); }
nextSongPositionStatus119 	int nextSongPosition() const { return mpd_status_get_next_song_pos(m_status.get()); }
nextSongIDStatus120 	int nextSongID() const { return mpd_status_get_next_song_id(m_status.get()); }
elapsedTimeStatus121 	unsigned elapsedTime() const { return mpd_status_get_elapsed_time(m_status.get()); }
totalTimeStatus122 	unsigned totalTime() const { return mpd_status_get_total_time(m_status.get()); }
kbpsStatus123 	unsigned kbps() const { return mpd_status_get_kbit_rate(m_status.get()); }
updateIDStatus124 	unsigned updateID() const { return mpd_status_get_update_id(m_status.get()); }
errorStatus125 	const char *error() const { return mpd_status_get_error(m_status.get()); }
126 
127 private:
StatusStatus128 	Status(mpd_status *status) : m_status(status, mpd_status_free) { }
129 
130 	std::shared_ptr<mpd_status> m_status;
131 };
132 
133 struct Directory
134 {
DirectoryDirectory135 	Directory()
136 	: m_last_modified(0)
137 	{ }
DirectoryDirectory138 	Directory(const mpd_directory *directory)
139 	{
140 		assert(directory != nullptr);
141 		m_path = mpd_directory_get_path(directory);
142 		m_last_modified = mpd_directory_get_last_modified(directory);
143 	}
144 	Directory(std::string path_, time_t last_modified = 0)
m_pathDirectory145 	: m_path(std::move(path_))
146 	, m_last_modified(last_modified)
147 	{ }
148 
149 	bool operator==(const Directory &rhs) const
150 	{
151 		return m_path == rhs.m_path
152 		    && m_last_modified == rhs.m_last_modified;
153 	}
154 	bool operator!=(const Directory &rhs) const
155 	{
156 		return !(*this == rhs);
157 	}
158 
pathDirectory159 	const std::string &path() const
160 	{
161 		return m_path;
162 	}
lastModifiedDirectory163 	time_t lastModified() const
164 	{
165 		return m_last_modified;
166 	}
167 
168 private:
169 	std::string m_path;
170 	time_t m_last_modified;
171 };
172 
173 struct Playlist
174 {
PlaylistPlaylist175 	Playlist()
176 	: m_last_modified(0)
177 	{ }
PlaylistPlaylist178 	Playlist(const mpd_playlist *playlist)
179 	{
180 		assert(playlist != nullptr);
181 		m_path = mpd_playlist_get_path(playlist);
182 		m_last_modified = mpd_playlist_get_last_modified(playlist);
183 	}
184 	Playlist(std::string path_, time_t last_modified = 0)
m_pathPlaylist185 	: m_path(std::move(path_))
186 	, m_last_modified(last_modified)
187 	{
188 		if (m_path.empty())
189 			throw std::runtime_error("empty path");
190 	}
191 
192 	bool operator==(const Playlist &rhs) const
193 	{
194 		return m_path == rhs.m_path
195 		    && m_last_modified == rhs.m_last_modified;
196 	}
197 	bool operator!=(const Playlist &rhs) const
198 	{
199 		return !(*this == rhs);
200 	}
201 
pathPlaylist202 	const std::string &path() const
203 	{
204 		return m_path;
205 	}
lastModifiedPlaylist206 	time_t lastModified() const
207 	{
208 		return m_last_modified;
209 	}
210 
211 private:
212 	std::string m_path;
213 	time_t m_last_modified;
214 };
215 
216 struct Item
217 {
218 	enum class Type { Directory, Song, Playlist };
219 
ItemItem220 	Item(mpd_entity *entity)
221 	{
222 		assert(entity != nullptr);
223 		switch (mpd_entity_get_type(entity))
224 		{
225 			case MPD_ENTITY_TYPE_DIRECTORY:
226 				m_type = Type::Directory;
227 				m_directory = Directory(mpd_entity_get_directory(entity));
228 				break;
229 			case MPD_ENTITY_TYPE_SONG:
230 				m_type = Type::Song;
231 				m_song = Song(mpd_song_dup(mpd_entity_get_song(entity)));
232 				break;
233 			case MPD_ENTITY_TYPE_PLAYLIST:
234 				m_type = Type::Playlist;
235 				m_playlist = Playlist(mpd_entity_get_playlist(entity));
236 				break;
237 			default:
238 				throw std::runtime_error("unknown mpd_entity type");
239 		}
240 		mpd_entity_free(entity);
241 	}
ItemItem242 	Item(Directory directory_)
243 	: m_type(Type::Directory)
244 	, m_directory(std::move(directory_))
245 	{ }
ItemItem246 	Item(Song song_)
247 	: m_type(Type::Song)
248 	, m_song(std::move(song_))
249 	{ }
ItemItem250 	Item(Playlist playlist_)
251 	: m_type(Type::Playlist)
252 	, m_playlist(std::move(playlist_))
253 	{ }
254 
255 	bool operator==(const Item &rhs) const
256 	{
257 		return m_directory == rhs.m_directory
258 		    && m_song == rhs.m_song
259 		    && m_playlist == rhs.m_playlist;
260 	}
261 	bool operator!=(const Item &rhs) const
262 	{
263 		return !(*this == rhs);
264 	}
265 
typeItem266 	Type type() const
267 	{
268 		return m_type;
269 	}
270 
directoryItem271 	Directory &directory()
272 	{
273 		return const_cast<Directory &>(
274 			static_cast<const Item &>(*this).directory());
275 	}
songItem276 	Song &song()
277 	{
278 		return const_cast<Song &>(
279 			static_cast<const Item &>(*this).song());
280 	}
playlistItem281 	Playlist &playlist()
282 	{
283 		return const_cast<Playlist &>(
284 			static_cast<const Item &>(*this).playlist());
285 	}
286 
directoryItem287 	const Directory &directory() const
288 	{
289 		assert(m_type == Type::Directory);
290 		return m_directory;
291 	}
songItem292 	const Song &song() const
293 	{
294 		assert(m_type == Type::Song);
295 		return m_song;
296 	}
playlistItem297 	const Playlist &playlist() const
298 	{
299 		assert(m_type == Type::Playlist);
300 		return m_playlist;
301 	}
302 
303 private:
304 	Type m_type;
305 	Directory m_directory;
306 	Song m_song;
307 	Playlist m_playlist;
308 };
309 
310 struct Output
311 {
OutputOutput312 	Output() { }
OutputOutput313 	Output(mpd_output *output)
314 	: m_output(output, mpd_output_free)
315 	{ }
316 
317 	bool operator==(const Output &rhs) const
318 	{
319 		if (empty() && rhs.empty())
320 			return true;
321 		else if (!empty() && !rhs.empty())
322 			return id() == rhs.id()
323 			    && strcmp(name(), rhs.name()) == 0
324 			    && enabled() == rhs.enabled();
325 		else
326 			return false;
327 	}
328 	bool operator!=(const Output &rhs) const
329 	{
330 		return !(*this == rhs);
331 	}
332 
idOutput333 	unsigned id() const
334 	{
335 		assert(m_output.get() != nullptr);
336 		return mpd_output_get_id(m_output.get());
337 	}
nameOutput338 	const char *name() const
339 	{
340 		assert(m_output.get() != nullptr);
341 		return mpd_output_get_name(m_output.get());
342 	}
enabledOutput343 	bool enabled() const
344 	{
345 		assert(m_output.get() != nullptr);
346 		return mpd_output_get_enabled(m_output.get());
347 	}
348 
emptyOutput349 	bool empty() const { return m_output.get() == nullptr; }
350 
351 private:
352 	std::shared_ptr<mpd_output> m_output;
353 };
354 
355 template <typename ObjectT>
356 struct Iterator: std::iterator<std::input_iterator_tag, ObjectT>
357 {
358 	// shared state of the iterator
359 	struct State
360 	{
361 		friend Iterator;
362 
363 		typedef std::function<bool(State &)> Fetcher;
364 
StateIterator::State365 		State(mpd_connection *connection_, Fetcher fetcher)
366 		: m_connection(connection_)
367 		, m_fetcher(fetcher)
368 		{
369 			assert(m_connection != nullptr);
370 			assert(m_fetcher != nullptr);
371 		}
~StateIterator::State372 		~State()
373 		{
374 			mpd_response_finish(m_connection);
375 		}
376 
connectionIterator::State377 		mpd_connection *connection() const
378 		{
379 			return m_connection;
380 		}
381 
setObjectIterator::State382 		void setObject(ObjectT object)
383 		{
384 			if (hasObject())
385 				*m_object = std::move(object);
386 			else
387 				m_object.reset(new ObjectT(std::move(object)));
388 		}
389 
390 	private:
391 		bool operator==(const State &rhs) const
392 		{
393 			return m_connection == rhs.m_connection
394 			    && m_object == m_object;
395 		}
396 		bool operator!=(const State &rhs) const
397 		{
398 			return !(*this == rhs);
399 		}
400 
fetchIterator::State401 		bool fetch()
402 		{
403 			return m_fetcher(*this);
404 		}
getObjectIterator::State405 		ObjectT &getObject() const
406 		{
407 			return *m_object;
408 		}
hasObjectIterator::State409 		bool hasObject() const
410 		{
411 			return m_object.get() != nullptr;
412 		}
413 
414 		mpd_connection *m_connection;
415 		Fetcher m_fetcher;
416 		std::unique_ptr<ObjectT> m_object;
417 	};
418 
IteratorIterator419 	Iterator()
420 	: m_state(nullptr)
421 	{ }
IteratorIterator422 	Iterator(mpd_connection *connection, typename State::Fetcher fetcher)
423 	: m_state(std::make_shared<State>(connection, std::move(fetcher)))
424 	{
425 		// get the first element
426 		++*this;
427 	}
~IteratorIterator428 	~Iterator()
429 	{
430 		if (m_state)
431 			checkConnectionErrors(m_state->connection());
432 	}
433 
finishIterator434 	void finish()
435 	{
436 		assert(m_state);
437 		// check errors and change the iterator into end iterator
438 		checkConnectionErrors(m_state->connection());
439 		m_state = nullptr;
440 	}
441 
442 	ObjectT &operator*() const
443 	{
444 		if (!m_state)
445 			throw std::runtime_error("no object associated with the iterator");
446 		assert(m_state->hasObject());
447 		return m_state->getObject();
448 	}
449 	ObjectT *operator->() const
450 	{
451 		return &**this;
452 	}
453 
454 	Iterator &operator++()
455 	{
456 		assert(m_state);
457 		if (!m_state->fetch())
458 			finish();
459 		return *this;
460 	}
461 	Iterator operator++(int)
462 	{
463 		Iterator it(*this);
464 		++*this;
465 		return it;
466 	}
467 
468 	bool operator==(const Iterator &rhs) const
469 	{
470 		return m_state == rhs.m_state;
471 	}
472 	bool operator!=(const Iterator &rhs) const
473 	{
474 		return !(*this == rhs);
475 	}
476 
477 private:
478 	std::shared_ptr<State> m_state;
479 };
480 
481 typedef Iterator<Directory> DirectoryIterator;
482 typedef Iterator<Item> ItemIterator;
483 typedef Iterator<Output> OutputIterator;
484 typedef Iterator<Playlist> PlaylistIterator;
485 typedef Iterator<Song> SongIterator;
486 typedef Iterator<std::string> StringIterator;
487 
488 struct Connection
489 {
490 	typedef std::function<void(int)> NoidleCallback;
491 
492 	Connection();
493 
494 	void Connect();
495 	bool Connected() const;
496 	void Disconnect();
497 
GetHostnameConnection498 	const std::string &GetHostname() { return m_host; }
GetPortConnection499 	int GetPort() { return m_port; }
500 
501 	unsigned Version() const;
502 
GetFDConnection503 	int GetFD() const { return m_fd; }
504 
505 	void SetHostname(const std::string &);
SetPortConnection506 	void SetPort(int port) { m_port = port; }
SetTimeoutConnection507 	void SetTimeout(int timeout) { m_timeout = timeout; }
SetPasswordConnection508 	void SetPassword(const std::string &password) { m_password = password; }
509 	void SendPassword();
510 
511 	Statistics getStatistics();
512 	Status getStatus();
513 
514 	void UpdateDirectory(const std::string &);
515 
516 	void Play();
517 	void Play(int);
518 	void PlayID(int);
519 	void Pause(bool);
520 	void Toggle();
521 	void Stop();
522 	void Next();
523 	void Prev();
524 	void Move(unsigned int from, unsigned int to);
525 	void Swap(unsigned, unsigned);
526 	void Seek(unsigned int pos, unsigned int where);
527 	void Shuffle();
528 	void ShuffleRange(unsigned start, unsigned end);
529 	void ClearMainPlaylist();
530 
531 	SongIterator GetPlaylistChanges(unsigned);
532 
533 	Song GetCurrentSong();
534 	Song GetSong(const std::string &);
535 	SongIterator GetPlaylistContent(const std::string &name);
536 	SongIterator GetPlaylistContentNoInfo(const std::string &name);
537 
538 	StringIterator GetSupportedExtensions();
539 
540 	void SetRepeat(bool);
541 	void SetRandom(bool);
542 	void SetSingle(bool);
543 	void SetConsume(bool);
544 	void SetCrossfade(unsigned);
545 	void SetVolume(unsigned int vol);
546 	void ChangeVolume(int change);
547 
548 	std::string GetReplayGainMode();
549 	void SetReplayGainMode(ReplayGainMode);
550 
551 	void SetPriority(const MPD::Song &s, int prio);
552 
553 	int AddSong(const std::string &, int = -1); // returns id of added song
554 	int AddSong(const Song &, int = -1); // returns id of added song
555 	bool AddRandomTag(mpd_tag_type, size_t, std::mt19937 &rng);
556 	bool AddRandomSongs(size_t number, const std::string &random_exclude_pattern, std::mt19937 &rng);
557 	bool Add(const std::string &path);
558 	void Delete(unsigned int pos);
559 	void DeleteRange(unsigned begin, unsigned end);
560 	void PlaylistDelete(const std::string &playlist, unsigned int pos);
561 	void StartCommandsList();
562 	void CommitCommandsList();
563 
564 	void DeletePlaylist(const std::string &name);
565 	bool LoadPlaylist(const std::string &name);
566 	void SavePlaylist(const std::string &);
567 	void ClearPlaylist(const std::string &playlist);
568 	void AddToPlaylist(const std::string &, const Song &);
569 	void AddToPlaylist(const std::string &, const std::string &);
570 	void PlaylistMove(const std::string &path, int from, int to);
571 	void Rename(const std::string &from, const std::string &to);
572 
573 	void StartSearch(bool);
574 	void StartFieldSearch(mpd_tag_type);
575 	void AddSearch(mpd_tag_type item, const std::string &str) const;
576 	void AddSearchAny(const std::string &str) const;
577 	void AddSearchURI(const std::string &str) const;
578 	SongIterator CommitSearchSongs();
579 
580 	PlaylistIterator GetPlaylists();
581 	StringIterator GetList(mpd_tag_type type);
582 	ItemIterator GetDirectory(const std::string &directory);
583 	SongIterator GetDirectoryRecursive(const std::string &directory);
584 	SongIterator GetSongs(const std::string &directory);
585 	DirectoryIterator GetDirectories(const std::string &directory);
586 
587 	OutputIterator GetOutputs();
588 	void EnableOutput(int id);
589 	void DisableOutput(int id);
590 
591 	StringIterator GetURLHandlers();
592 	StringIterator GetTagTypes();
593 
594 	void idle();
595 	int noidle();
596 	void setNoidleCallback(NoidleCallback callback);
597 
598 private:
599 	struct ConnectionDeleter {
operatorConnection::ConnectionDeleter600 		void operator()(mpd_connection *connection) {
601 			mpd_connection_free(connection);
602 		}
603 	};
604 
605 	void checkConnection() const;
606 	void prechecks();
607 	void prechecksNoCommandsList();
608 	void checkErrors() const;
609 
610 	NoidleCallback m_noidle_callback;
611 	std::unique_ptr<mpd_connection, ConnectionDeleter> m_connection;
612 	bool m_command_list_active;
613 
614 	int m_fd;
615 	bool m_idle;
616 
617 	std::string m_host;
618 	int m_port;
619 	int m_timeout;
620 	std::string m_password;
621 };
622 
623 }
624 
625 extern MPD::Connection Mpd;
626 
627 #endif // NCMPCPP_MPDPP_H
628