1 /* 2 * Cantata 3 * 4 * Copyright (c) 2011-2020 Craig Drummond <craig.p.drummond@gmail.com> 5 * 6 */ 7 /* 8 * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and 9 * Roeland Douma (roeland AT rullzer DOT com) 10 * 11 * This file is part of QtMPC. 12 * 13 * QtMPC is free software: you can redistribute it and/or modify 14 * it under the terms of the GNU General Public License as published by 15 * the Free Software Foundation, either version 2 of the License, or 16 * (at your option) any later version. 17 * 18 * QtMPC is distributed in the hope that it will be useful, 19 * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 * GNU General Public License for more details. 22 * 23 * You should have received a copy of the GNU General Public License 24 * along with QtMPC. If not, see <http://www.gnu.org/licenses/>. 25 */ 26 27 #ifndef MPDCONNECTION_H 28 #define MPDCONNECTION_H 29 30 #include <QTcpSocket> 31 #include <QLocalSocket> 32 #include <QHostAddress> 33 #include <QNetworkProxy> 34 #include <QStringList> 35 #include <QSet> 36 #include "mpdstats.h" 37 #include "mpdstatus.h" 38 #include "song.h" 39 #include "output.h" 40 #include "playlist.h" 41 #include "stream.h" 42 #include "config.h" 43 #include <time.h> 44 45 class QTimer; 46 class Thread; 47 class QPropertyAnimation; 48 49 class MpdSocket : public QObject 50 { 51 Q_OBJECT 52 public: 53 MpdSocket(QObject *parent); 54 ~MpdSocket() override; 55 56 void connectToHost(const QString &hostName, quint16 port, QIODevice::OpenMode mode = QIODevice::ReadWrite); disconnectFromHost()57 void disconnectFromHost() { 58 if (tcp) { 59 tcp->disconnectFromHost(); 60 } else if(local) { 61 local->disconnectFromServer(); 62 } 63 } close()64 void close() { 65 if (tcp) { 66 tcp->close(); 67 } else if(local) { 68 local->close(); 69 } 70 } write(const QByteArray & data)71 int write(const QByteArray &data) { 72 if (tcp) { 73 return tcp->write(data); 74 } else if(local) { 75 return local->write(data); 76 } 77 return 0; 78 } 79 void waitForBytesWritten(int msecs = 30000) { 80 if (tcp) { 81 tcp->waitForBytesWritten(msecs); 82 } else if(local) { 83 local->waitForBytesWritten(msecs); 84 } 85 } 86 bool waitForReadyRead(int msecs = 30000) { 87 return tcp ? tcp->waitForReadyRead(msecs) 88 : local 89 ? local->waitForReadyRead(msecs) 90 : false; 91 } 92 bool waitForConnected(int msecs = 30000) { 93 return tcp ? tcp->waitForConnected(msecs) 94 : local 95 ? local->waitForConnected(msecs) 96 : false; 97 } bytesAvailable()98 qint64 bytesAvailable() { 99 return tcp ? tcp->bytesAvailable() 100 : local 101 ? local->bytesAvailable() 102 : 0; 103 } readAll()104 QByteArray readAll() { 105 return tcp ? tcp->readAll() 106 : local 107 ? local->readAll() 108 : QByteArray(); 109 } state()110 QAbstractSocket::SocketState state() const { 111 return tcp ? tcp->state() 112 : local 113 ? (QAbstractSocket::SocketState)local->state() 114 : QAbstractSocket::UnconnectedState; 115 } proxyType()116 QNetworkProxy::ProxyType proxyType() const { return tcp ? tcp->proxy().type() : QNetworkProxy::NoProxy; } isLocal()117 bool isLocal() const { return nullptr!=local; } address()118 QString address() const { return tcp ? tcp->peerAddress().toString() : QString(); } errorString()119 QString errorString() const { return tcp ? tcp->errorString() : local ? local->errorString() : QLatin1String("No socket object?"); } error()120 QAbstractSocket::SocketError error() const { 121 return tcp ? tcp->error() 122 : local 123 ? (QAbstractSocket::SocketError)local->error() 124 : QAbstractSocket::UnknownSocketError; 125 } 126 Q_SIGNALS: 127 void stateChanged(QAbstractSocket::SocketState state); 128 void readyRead(); 129 130 private Q_SLOTS: 131 void localStateChanged(QLocalSocket::LocalSocketState state); 132 133 private: 134 void deleteTcp(); 135 void deleteLocal(); 136 137 private: 138 QTcpSocket *tcp; 139 QLocalSocket *local; 140 }; 141 142 struct MPDConnectionDetails { 143 MPDConnectionDetails(); MPDConnectionDetailsMPDConnectionDetails144 MPDConnectionDetails(const MPDConnectionDetails &o) { *this=o; } 145 QString getName() const; 146 QString description() const; isLocalMPDConnectionDetails147 bool isLocal() const { return hostname.startsWith('/') /*|| hostname.startsWith('@')*/; } isEmptyMPDConnectionDetails148 bool isEmpty() const { return hostname.isEmpty() || (!isLocal() && 0==port); } 149 MPDConnectionDetails & operator=(const MPDConnectionDetails &o); 150 bool operator==(const MPDConnectionDetails &o) const { return hostname==o.hostname && isLocal()==o.isLocal() && (isLocal() || port==o.port) && password==o.password; } 151 bool operator!=(const MPDConnectionDetails &o) const { return !(*this==o); } 152 bool operator<(const MPDConnectionDetails &o) const { return name.localeAwareCompare(o.name)<0; } 153 static QString configGroupName(const QString &n=QString()) { return n.isEmpty() ? "Connection" : ("Connection-"+n); } 154 void setDirReadable(); 155 156 QString name; 157 QString hostname; 158 quint16 port; 159 QString password; 160 QString dir; 161 bool dirReadable; 162 #ifdef ENABLE_HTTP_STREAM_PLAYBACK 163 QString streamUrl; 164 #endif 165 QString replayGain; 166 bool applyReplayGain; 167 bool allowLocalStreaming; 168 bool autoUpdate; 169 }; 170 171 class MPDServerInfo { 172 public: 173 enum ServerType { 174 Undetermined, 175 Mpd, 176 Mopidy, 177 ForkedDaapd, 178 Unknown 179 }; 180 MPDServerInfo()181 MPDServerInfo() { 182 reset(); 183 lsinfoCommand = "lsinfo \"mpd-client://cantata/"; 184 lsinfoCommand += PACKAGE_VERSION_STRING; 185 lsinfoCommand += "\""; 186 }; 187 188 void reset(); 189 void detect(); 190 getServerType()191 ServerType getServerType() const { return serverType;} isUndetermined()192 bool isUndetermined() const { return serverType == Undetermined; } isMpd()193 bool isMpd() const { return serverType == Mpd; } isMopidy()194 bool isMopidy() const { return serverType == Mopidy; } isForkedDaapd()195 bool isForkedDaapd() const { return serverType == ForkedDaapd; } isPlayQueueIdValid()196 bool isPlayQueueIdValid() const { return serverType != ForkedDaapd; } 197 getServerName()198 const QString &getServerName() const { return serverName;} getTopLevelLsinfo()199 const QByteArray &getTopLevelLsinfo() const { return topLevelLsinfo; } 200 201 private: setServerType(ServerType newServerType)202 void setServerType(ServerType newServerType) { serverType = newServerType; } 203 204 ServerType serverType; 205 QString serverName; 206 QByteArray topLevelLsinfo; 207 208 struct ResponseParameter { 209 QByteArray response; 210 bool isSubstring; 211 ServerType serverType; 212 QString name; 213 }; 214 215 QByteArray lsinfoCommand; 216 static ResponseParameter lsinfoResponseParameters[]; 217 }; 218 219 class MPDConnection : public QObject 220 { 221 Q_OBJECT 222 Q_PROPERTY(int volume READ getVolume WRITE setVolume) 223 224 public: 225 enum AddAction 226 { 227 Append, 228 Replace, 229 ReplaceAndplay, 230 AppendAndPlay, 231 AddAndPlay, 232 AddAfterCurrent 233 }; 234 235 enum VolumeFade 236 { 237 MinFade = 400, 238 MaxFade = 4000, 239 DefaultFade = MinFade // disable volume fade by default. prev:2000 240 }; 241 242 static const QString constModifiedSince; 243 static const int constMaxPqChanges; 244 static const QString constStreamsPlayListName; 245 static const QString constPlaylistPrefix; 246 static const QString constDirPrefix; 247 248 static MPDConnection * self(); 249 static QByteArray quote(int val); 250 static QByteArray encodeName(const QString &name); 251 252 struct Response { 253 Response(bool o=true, const QByteArray &d=QByteArray()); 254 QString getError(const QByteArray &command); 255 bool ok; 256 QByteArray data; 257 }; 258 259 static void enableDebug(); 260 261 MPDConnection(); 262 ~MPDConnection() override; 263 264 void start(); getDetails()265 const MPDConnectionDetails & getDetails() const { return details; } setDirReadable()266 void setDirReadable() { details.setDirReadable(); } isConnected()267 bool isConnected() const { return State_Connected==state; } canUsePriority()268 bool canUsePriority() const { return ver>=CANTATA_MAKE_VERSION(0, 17, 0) && isMpd(); } urlHandlers()269 const QSet<QString> & urlHandlers() const { return handlers; } tags()270 const QSet<QString> & tags() const { return tagTypes; } composerTagSupported()271 bool composerTagSupported() const { return tagTypes.contains(QLatin1String("Composer")); } commentTagSupported()272 bool commentTagSupported() const { return tagTypes.contains(QLatin1String("Comment")); } performerTagSupported()273 bool performerTagSupported() const { return tagTypes.contains(QLatin1String("Performer")); } originalDateTagSupported()274 bool originalDateTagSupported() const { return tagTypes.contains(QLatin1String("OriginalDate")); } modifiedFindSupported()275 bool modifiedFindSupported() const { return ver>=CANTATA_MAKE_VERSION(0, 19, 0); } replaygainSupported()276 bool replaygainSupported() const { return ver>=CANTATA_MAKE_VERSION(0, 16, 0); } supportsCoverDownload()277 bool supportsCoverDownload() const { return ver>=CANTATA_MAKE_VERSION(0, 21, 0) && isMpd(); } 278 bool localFilePlaybackSupported() const; stickersSupported()279 bool stickersSupported() const { return canUseStickers; } 280 version()281 long version() const { return ver; } 282 static bool isPlaylist(const QString &file); unmuteVolume()283 int unmuteVolume() { return unmuteVol; } isMuted()284 bool isMuted() { return -1!=unmuteVol; } isMpd()285 bool isMpd() const { return serverInfo.isMpd(); } isMopidy()286 bool isMopidy() const { return serverInfo.isMopidy(); } isForkedDaapd()287 bool isForkedDaapd() const { return serverInfo.isForkedDaapd(); } isPlayQueueIdValid()288 bool isPlayQueueIdValid() const { return serverInfo.isPlayQueueIdValid(); } setVolumeFadeDuration(int f)289 void setVolumeFadeDuration(int f) { fadeDuration=f; } ipAddress()290 QString ipAddress() const { return details.isLocal() ? QString() : sock.address(); } 291 292 public Q_SLOTS: 293 void stop(); 294 void reconnect(); 295 void setDetails(const MPDConnectionDetails &d); 296 // void disconnectMpd(); 297 // Current Playlist 298 void add(const QStringList &files, int action, quint8 priority, bool decreasePriority); 299 void add(const QStringList &files, quint32 pos, quint32 size, int action, quint8 priority, bool decreasePriority); 300 void add(const QStringList &files, quint32 pos, quint32 size, int action, const QList<quint8> &priority); 301 void add(const QStringList &files, quint32 pos, quint32 size, int action, QList<quint8> priority, bool decreasePriority); 302 void populate(const QStringList &files, const QList<quint8> &priority); 303 void addAndPlay(const QString &file); 304 void currentSong(); 305 void playListChanges(); 306 void playListInfo(); 307 void removeSongs(const QList<qint32> &items); 308 void move(quint32 from, quint32 to); 309 void move(const QList<quint32> &items, quint32 pos, quint32 size); 310 void setOrder(const QList<quint32> &items); 311 void shuffle(quint32 from, quint32 to); 312 void clear(); 313 void shuffle(); 314 315 // Playback 316 void setCrossFade(int secs); 317 void setReplayGain(const QString &v); 318 void getReplayGain(); 319 void goToNext(); 320 void setPause(bool toggle); 321 void play(); 322 void startPlayingSong(quint32 song = 0); 323 void startPlayingSongId(qint32 songId = 0); 324 void goToPrevious(); 325 void setConsume(bool toggle); 326 void setRandom(bool toggle); 327 void setRepeat(bool toggle); 328 void setSingle(bool toggle); 329 void setSeek(quint32 song, quint32 time); 330 void setSeekId(qint32 songId, quint32 time); 331 void setVolume(int vol); 332 void toggleMute(); 333 void stopPlaying(bool afterCurrent=false); 334 void clearStopAfter(); 335 336 // Output 337 void outputs(); 338 void enableOutput(quint32 id, bool enable); 339 340 // Miscellaneous 341 void getStats(); 342 void getStatus(); 343 void getUrlHandlers(); 344 void getTagTypes(); 345 void getCover(const Song &song); 346 347 // Database 348 void loadLibrary(); 349 void listFolder(const QString &folder); 350 351 // Admin 352 void updateMaybe(); 353 void update(); 354 355 // Playlists 356 // void listPlaylist(const QString &name); 357 void listPlaylists(); 358 void playlistInfo(const QString &name); 359 void loadPlaylist(const QString &name, bool replace); 360 void renamePlaylist(const QString oldName, const QString newName); 361 void removePlaylist(const QString &name); 362 void savePlaylist(const QString &name, bool overwrite); addToPlaylist(const QString & name,const QStringList & songs)363 void addToPlaylist(const QString &name, const QStringList &songs) { addToPlaylist(name, songs, 0, 0); } 364 void addToPlaylist(const QString &name, const QStringList &songs, quint32 pos, quint32 size); 365 void removeFromPlaylist(const QString &name, const QList<quint32> &positions); 366 void moveInPlaylist(const QString &name, const QList<quint32> &items, quint32 row, quint32 size); 367 368 void setPriority(const QList<qint32> &ids, quint8 priority, bool decreasePriority); 369 370 void search(const QString &field, const QString &value, int id); 371 void search(const QByteArray &query, const QString &id); 372 373 void listStreams(); 374 void saveStream(const QString &url, const QString &name); 375 void removeStreams(const QList<quint32> &positions); 376 void editStream(const QString &url, const QString &name, quint32 position); 377 378 void sendClientMessage(const QString &channel, const QString &msg, const QString &clientName); 379 void sendDynamicMessage(const QStringList &msg); 380 int getVolume(); 381 382 void setRating(const QString &file, quint8 val); 383 void setRating(const QStringList &files, quint8 val); 384 void getRating(const QString &file); 385 386 void seek(qint32 offset=0); 387 388 Q_SIGNALS: 389 void connectionChanged(const MPDConnectionDetails &details); 390 void connectionNotChanged(const QString &name); 391 void stateChanged(bool connected); 392 void passwordError(); 393 void currentSongUpdated(const Song &song); 394 void playlistUpdated(const QList<Song> &songs, bool isComplete); 395 void statsUpdated(const MPDStatsValues &stats); 396 void statusUpdated(const MPDStatusValues &status); 397 void outputsUpdated(const QList<Output> &outputs); 398 void librarySongs(QList<Song> *songs); 399 void folderContents(const QString &folder, const QStringList &subFolders, const QList<Song> &songs); 400 void playlistsRetrieved(const QList<Playlist> &data); 401 void playlistInfoRetrieved(const QString &name, const QList<Song> &songs); 402 void playlistRenamed(const QString &from, const QString &to); 403 void removedFromPlaylist(const QString &name, const QList<quint32> &positions); 404 void movedInPlaylist(const QString &name, const QList<quint32> &items, quint32 pos); 405 void updatingDatabase(); 406 void updatedDatabase(); 407 void playlistLoaded(const QString &playlist); 408 void added(const QStringList &files); 409 void replayGain(const QString &); 410 void updatingLibrary(time_t dbUpdate); 411 void updatedLibrary(); 412 void updatingFileList(); 413 void updatedFileList(); 414 void error(const QString &err, bool showActions=false); 415 void info(const QString &msg); 416 void dirChanged(); 417 void prioritySet(const QMap<qint32, quint8> &tracks); 418 419 void stopAfterCurrentChanged(bool afterCurrent); 420 void streamUrl(const QString &url); 421 422 void searchResponse(int id, const QList<Song> &songs); 423 void searchResponse(const QString &id, const QList<Song> &songs); 424 425 void socketAddress(const QString &addr); 426 void cantataStreams(const QStringList &files); 427 void cantataStreams(const QList<Song> &songs, bool isUpdate); 428 void removedIds(const QSet<qint32> &ids); 429 430 void savedStream(const QString &url, const QString &name); 431 void removedStreams(const QList<quint32> &removed); 432 void editedStream(const QString &url, const QString &name, quint32 position); 433 void streamList(const QList<Stream> &streams); 434 435 void clientMessageFailed(const QString &client, const QString &msg); 436 void dynamicSupport(bool e); 437 void dynamicResponse(const QStringList &resp); 438 439 void rating(const QString &file, quint8 val); 440 void stickerDbChanged(); 441 442 void ifaceIp(const QString &addr); 443 444 void albumArt(const Song &song, const QByteArray &data); 445 446 private Q_SLOTS: 447 void idleDataReady(); 448 void onSocketStateChanged(QAbstractSocket::SocketState socketState); 449 450 private: 451 enum ConnectionReturn 452 { 453 Success, 454 Failed, 455 ProxyError, 456 IncorrectPassword 457 }; 458 459 static ConnectionReturn convertSocketCode(MpdSocket &socket); 460 QString errorString(ConnectionReturn status) const; 461 ConnectionReturn connectToMPD(); 462 void disconnectFromMPD(); 463 ConnectionReturn connectToMPD(MpdSocket &socket, bool enableIdle=false); 464 Response sendCommand(const QByteArray &command, bool emitErrors=true, bool retry=true); 465 void initialize(); 466 void parseIdleReturn(const QByteArray &data); 467 bool doMoveInPlaylist(const QString &name, const QList<quint32> &items, quint32 pos, quint32 size); 468 void toggleStopAfterCurrent(bool afterCurrent); 469 bool recursivelyListDir(const QString &dir, QList<Song> &songs); 470 QStringList getPlaylistFiles(const QString &name); 471 QStringList getAllFiles(const QString &dir); 472 bool checkRemoteDynamicSupport(); 473 bool subscribe(const QByteArray &channel); 474 void setupRemoteDynamic(); 475 void readRemoteDynamicMessages(); 476 bool fadingVolume(); 477 bool startVolumeFade(); 478 void stopVolumeFade(); 479 void emitStatusUpdated(MPDStatusValues &v); 480 void clearError(); 481 void getRatings(QList<Song> &songs); 482 void getStickerSupport(); 483 void playFirstTrack(bool emitErrors); 484 void determineIfaceIp(); 485 486 private: 487 bool isInitialConnect; 488 Thread *thread; 489 long ver; 490 QSet<QString> handlers; 491 QSet<QString> tagTypes; 492 bool canUseStickers; 493 MPDConnectionDetails details; 494 time_t dbUpdate; 495 // Use 2 sockets, 1 for commands and 1 to receive MPD idle events. 496 // Cant use 1, as we could write a command just as an idle event is ready to read 497 MpdSocket sock; 498 MpdSocket idleSocket; 499 QTimer *connTimer; 500 QByteArray dynamicId; 501 502 // The three items are used so that we can do quick playqueue updates... 503 QList<qint32> playQueueIds; 504 QSet<qint32> streamIds; 505 quint32 lastStatusPlayQueueVersion; 506 quint32 lastUpdatePlayQueueVersion; 507 508 enum State 509 { 510 State_Blank, 511 State_Connected, 512 State_Disconnected 513 }; 514 State state; 515 bool isListingMusic; 516 QTimer *reconnectTimer; 517 time_t reconnectStart; 518 519 bool stopAfterCurrent; 520 qint32 currentSongId; 521 quint32 songPos; // USe for stop-after-current when we only have 1 songin playqueue! 522 int unmuteVol; 523 friend class MPDServerInfo; 524 MPDServerInfo serverInfo; 525 bool isUpdatingDb; 526 527 QPropertyAnimation *volumeFade; 528 int fadeDuration; 529 int restoreVolume; 530 }; 531 532 #endif 533