-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Livemetadata PR #1675
[WIP] Livemetadata PR #1675
Changes from 51 commits
32cece7
ba829a2
5169748
3d8c501
523d09b
23ef862
0a4a4d6
d203e85
1a6bc0d
ff27795
db2db23
7f55150
121443e
2cd5bbc
b9c4a6c
e10df9e
c1de7db
50019b0
d805306
4e714d5
b3f8ea8
8cd9c16
d4b9aa5
ec98ef6
0209b4a
2d8f4a2
88c4819
ab8b524
97daf77
4bc2de3
49b8b93
567e9b5
5748fa9
3a677b6
9c43d09
2c21183
a6d67d7
ba8a6fc
6b7f207
f7c6a83
1057b06
152925c
ad43628
751f567
64d35c5
ddfb859
e4dc8df
a1ad4c6
ee4c5a5
4571d43
185d2f2
0bc22be
0af2279
6be0042
62eae49
c2317c7
8b5e54a
cac8c1f
6807838
d6880a1
32e565a
1b77c8b
7ba10a7
94ab525
5e8fb60
41bdfb9
0def3e3
9fe5277
97271d8
f4b9649
dde6c47
e1cfd7a
69a3020
7af4dd7
170e6c9
8c6fa58
899f207
4a13665
39dc901
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
|
||
#include <QTextCodec> | ||
#include <QtConcurrentRun> | ||
|
||
#include "broadcast/filelistener/filelistener.h" | ||
#include "broadcast/filelistener/metadatafileworker.h" | ||
#include "preferences/metadatafilesettings.h" | ||
|
||
|
||
FileListener::FileListener(UserSettingsPointer pConfig) | ||
: m_COsettingsChanged(kFileSettingsChanged), | ||
m_pConfig(pConfig), | ||
m_latestSettings(MetadataFileSettings::getPersistedSettings(pConfig)), | ||
m_filePathChanged(false), | ||
m_tracksPaused(false) { | ||
|
||
MetadataFileWorker *newWorker = new MetadataFileWorker(m_latestSettings.filePath); | ||
newWorker->moveToThread(&m_workerThread); | ||
|
||
connect(&m_workerThread,SIGNAL(finished()), | ||
newWorker,SLOT(deleteLater())); | ||
|
||
connect(this,SIGNAL(deleteFile()), | ||
newWorker,SLOT(slotDeleteFile())); | ||
|
||
connect(this,SIGNAL(moveFile(QString)), | ||
newWorker,SLOT(slotMoveFile(QString))); | ||
|
||
connect(this,SIGNAL(writeMetadataToFile(QByteArray)), | ||
newWorker,SLOT(slotWriteMetadataToFile(QByteArray))); | ||
|
||
connect(this,SIGNAL(clearFile()), | ||
newWorker,SLOT(slotClearFile())); | ||
|
||
connect(&m_COsettingsChanged,SIGNAL(valueChanged(double)), | ||
this,SLOT(slotFileSettingsChanged(double))); | ||
|
||
m_workerThread.start(); | ||
} | ||
|
||
FileListener::~FileListener() { | ||
m_workerThread.quit(); | ||
m_workerThread.wait(); | ||
} | ||
|
||
|
||
void FileListener::slotBroadcastCurrentTrack(TrackPointer pTrack) { | ||
if (!pTrack) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this really happen? If not, we should use VERIFY_AND_DEBUG_ASSERT. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this happens every time a track is paused. The signal emitted by PlayerInfo passes a null pointer when the track is paused. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the AllTracksPaused signal redundant? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, perhaps you're right... the advantage of that slot is that it is immediate. But perhaps it doesn't matter. I don't know... |
||
return; | ||
m_fileContents.title = pTrack->getTitle(); | ||
m_fileContents.artist = pTrack->getArtist(); | ||
QString writtenString(m_latestSettings.fileFormatString); | ||
writtenString.replace("$author",pTrack->getArtist()). | ||
replace("$title",pTrack->getTitle()) += '\n'; | ||
QTextCodec *codec = QTextCodec::codecForName(m_latestSettings.fileEncoding); | ||
DEBUG_ASSERT(codec); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should handle this gracefully. Eg use local8bit () There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In reality it should never be null, but yeah, perhaps a warning and a fallback codec is better. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We try to a avoid plain debug asserts, to not risk a crash in case we do a release build. Remember that this line is gone in release builds. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is changed by an if in the newest commit. Perhaps I should check all other DEBUG_ASSERTS There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use VERIFY_OR_DEBUG_ASSERT this is an if(!... in case of release version and an assert in case of debug build. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alright, thanks. |
||
QByteArray fileContents = codec->fromUnicode(writtenString); | ||
m_tracksPaused = false; | ||
emit writeMetadataToFile(fileContents); | ||
} | ||
|
||
void FileListener::slotScrobbleTrack(TrackPointer pTrack) { | ||
Q_UNUSED(pTrack); | ||
} | ||
|
||
void FileListener::slotAllTracksPaused() { | ||
m_tracksPaused = true; | ||
emit clearFile(); | ||
} | ||
|
||
void FileListener::slotFileSettingsChanged(double value) { | ||
if (value) { | ||
FileSettings latestSettings = MetadataFileSettings::getLatestSettings(); | ||
m_filePathChanged = latestSettings.filePath != m_latestSettings.filePath; | ||
m_latestSettings = latestSettings; | ||
updateStateFromSettings(); | ||
} | ||
} | ||
|
||
void FileListener::updateStateFromSettings() { | ||
if (m_latestSettings.enabled) { | ||
updateFile(); | ||
} | ||
else { | ||
emit deleteFile(); | ||
} | ||
} | ||
|
||
void FileListener::updateFile() { | ||
if (m_filePathChanged) { | ||
emit moveFile(m_latestSettings.filePath); | ||
m_filePathChanged = false; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think m_filePathChanged = false; is missing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, thanks. |
||
if (!m_tracksPaused && !m_fileContents.isEmpty()) { | ||
QTextCodec *codec = QTextCodec::codecForName(m_latestSettings.fileEncoding); | ||
if (!codec) { | ||
qWarning() << "Text codec selected from metadata broadcast settings doesn't exist"; | ||
codec = QTextCodec::codecForName("UTF-8"); | ||
} | ||
QString newContents(m_latestSettings.fileFormatString); | ||
newContents.replace("$author",m_fileContents.artist) | ||
.replace("$title",m_fileContents.title) += '\n'; | ||
QByteArray contentsBinary = codec->fromUnicode(newContents); | ||
emit writeMetadataToFile(contentsBinary); | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
#pragma once | ||
|
||
#include <QFile> | ||
#include <QThread> | ||
#include "preferences/dialog/dlgprefmetadata.h" | ||
#include "control/controlpushbutton.h" | ||
#include "broadcast/scrobblingservice.h" | ||
|
||
class FileListener: public ScrobblingService { | ||
Q_OBJECT | ||
public: | ||
explicit FileListener(UserSettingsPointer pSettings); | ||
~FileListener() override; | ||
void slotBroadcastCurrentTrack(TrackPointer pTrack) override; | ||
void slotScrobbleTrack(TrackPointer pTrack) override; | ||
void slotAllTracksPaused() override; | ||
signals: | ||
void deleteFile(); | ||
void moveFile(QString destination); | ||
void writeMetadataToFile(QByteArray contents); | ||
void clearFile(); | ||
private slots: | ||
void slotFileSettingsChanged(double value); | ||
private: | ||
|
||
struct WrittenMetadata { | ||
QString title,artist; | ||
bool isEmpty() { | ||
return title.isEmpty() && artist.isEmpty(); | ||
} | ||
}; | ||
|
||
void updateStateFromSettings(); | ||
void updateFile(); | ||
|
||
ControlPushButton m_COsettingsChanged; | ||
UserSettingsPointer m_pConfig; | ||
FileSettings m_latestSettings; | ||
QThread m_workerThread; | ||
WrittenMetadata m_fileContents; | ||
bool m_filePathChanged; | ||
bool m_tracksPaused; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
|
||
#include "broadcast/filelistener/metadatafileworker.h" | ||
|
||
MetadataFileWorker::MetadataFileWorker(const QString &filePath) | ||
: m_file(filePath) { | ||
|
||
} | ||
|
||
void MetadataFileWorker::slotDeleteFile() { | ||
m_file.remove(); | ||
} | ||
|
||
void MetadataFileWorker::slotMoveFile(QString destination) { | ||
m_file.remove(); | ||
m_file.setFileName(destination); | ||
} | ||
|
||
void MetadataFileWorker::slotWriteMetadataToFile(QByteArray fileContents) { | ||
m_file.open(QIODevice::WriteOnly | | ||
QIODevice::Text | | ||
QIODevice::Unbuffered); | ||
m_file.write(fileContents); | ||
m_file.close(); | ||
} | ||
|
||
void MetadataFileWorker::slotClearFile() { | ||
m_file.resize(0); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
#pragma once | ||
|
||
#include <QObject> | ||
#include <QFile> | ||
|
||
class MetadataFileWorker : public QObject { | ||
Q_OBJECT | ||
public: | ||
explicit MetadataFileWorker(const QString &filePath); | ||
public slots: | ||
void slotDeleteFile(); | ||
void slotMoveFile(QString destination); | ||
void slotWriteMetadataToFile(QByteArray fileContents); | ||
void slotClearFile(); | ||
private: | ||
QFile m_file; | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
|
||
#include <QDateTime> | ||
#include <QJsonArray> | ||
#include <QJsonDocument> | ||
#include <QJsonObject> | ||
|
||
#include "broadcast/listenbrainzlistener/listenbrainzjsonfactory.h" | ||
|
||
|
||
QByteArray ListenBrainzJSONFactory::getJSONFromTrack(TrackPointer pTrack, JsonType type) { | ||
QJsonObject jsonObject; | ||
QString stringType; | ||
if (type == NowListening) { | ||
stringType = "playing_now"; | ||
} | ||
else { | ||
stringType = "single"; | ||
} | ||
|
||
QJsonArray payloadArray; | ||
QJsonObject payloadObject; | ||
QJsonObject metadataObject; | ||
QString title = pTrack->getTitle(); | ||
QString artist = pTrack->getArtist(); | ||
metadataObject.insert("artist_name",artist); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mit Pick: move comma ...name", art... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alright. |
||
metadataObject.insert("track_name",title); | ||
payloadObject.insert("track_metadata",metadataObject); | ||
qint64 timeStamp = QDateTime::currentMSecsSinceEpoch() / 1000; | ||
|
||
if (type == Single) { | ||
payloadObject.insert("listened_at",timeStamp); | ||
} | ||
payloadArray.append(payloadObject); | ||
jsonObject.insert("listen_type",stringType); | ||
jsonObject.insert("payload",payloadArray); | ||
QJsonDocument doc(jsonObject); | ||
return doc.toJson(QJsonDocument::Compact); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
#pragma once | ||
|
||
#include <QByteArray> | ||
|
||
#include "track/track.h" | ||
|
||
namespace ListenBrainzJSONFactory { | ||
enum JsonType {NowListening, Single}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can become an enum class. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is outdated, I don't use this enum anymore since the format is entirely given by the config object. |
||
QByteArray getJSONFromTrack(TrackPointer pTrack, JsonType type); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
|
||
#include <QJsonObject> | ||
|
||
#include "preferences/listenbrainzsettings.h" | ||
#include "broadcast/listenbrainzlistener/listenbrainzservice.h" | ||
#include "broadcast/listenbrainzlistener/listenbrainzjsonfactory.h" | ||
#include "broadcast/listenbrainzlistener/networkmanager.h" | ||
|
||
ListenBrainzService::ListenBrainzService(UserSettingsPointer pSettings) | ||
: m_request(ListenBrainzAPIURL), | ||
m_latestSettings(ListenBrainzSettingsManager::getPersistedSettings(pSettings)), | ||
m_COSettingsChanged(kListenBrainzSettingsChanged) { | ||
connect(&m_manager,&QNetworkAccessManager::finished, | ||
this,&ListenBrainzService::slotAPICallFinished); | ||
connect(&m_COSettingsChanged,&ControlPushButton::valueChanged, | ||
this,&ListenBrainzService::slotSettingsChanged); | ||
m_request.setHeader(QNetworkRequest::ContentTypeHeader,"application/json"); | ||
if (m_latestSettings.enabled) { | ||
m_request.setRawHeader("Authorization","Token " + m_latestSettings.userToken.toUtf8()); | ||
} | ||
} | ||
|
||
|
||
void ListenBrainzService::slotBroadcastCurrentTrack(TrackPointer pTrack) { | ||
Q_UNUSED(pTrack); | ||
/*if (!pTrack || !m_latestSettings.enabled) | ||
return; | ||
m_currentJSON = new QByteArray( | ||
ListenBrainzJSONFactory::getJSONFromTrack( | ||
pTrack,ListenBrainzJSONFactory::NowListening)); | ||
m_manager.post(m_request,*m_currentJSON);*/ | ||
} | ||
|
||
void ListenBrainzService::slotScrobbleTrack(TrackPointer pTrack) { | ||
if (!pTrack || !m_latestSettings.enabled) | ||
return; | ||
m_currentJSON = | ||
ListenBrainzJSONFactory::getJSONFromTrack( | ||
pTrack,ListenBrainzJSONFactory::Single); | ||
m_manager.post(m_request,m_currentJSON); | ||
} | ||
|
||
void ListenBrainzService::slotAllTracksPaused() { | ||
|
||
} | ||
|
||
void ListenBrainzService::slotAPICallFinished(QNetworkReply *reply) { | ||
if (reply->error() != QNetworkReply::NoError) { | ||
qWarning() << "API call to ListenBrainz error: " << | ||
reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); | ||
} | ||
m_currentJSON.clear(); | ||
} | ||
|
||
void ListenBrainzService::slotSettingsChanged(double value) { | ||
if (value) { | ||
m_latestSettings = ListenBrainzSettingsManager::getLatestSettings(); | ||
if (m_latestSettings.enabled) { | ||
m_request.setRawHeader("Authorization","Token " + m_latestSettings.userToken.toUtf8()); | ||
} | ||
} | ||
} | ||
|
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove extra new-lines |
||
|
||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is this one deleted?
You can make it a patented_ptr to let Qt take care of this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the signal finished from the thread is emitted the object is deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is hard to spot. Where exactly is it?
We should follow the rule to pass the pointer with the ownership. Since this is a QObject, the qt object tee may works best as an owner.