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