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