WIP adding support for tracking network requests.

This commit is contained in:
John Maguire 2015-10-01 16:42:25 +01:00
parent daddbdea96
commit c1e2753c9d
22 changed files with 471 additions and 19 deletions

View File

@ -24,8 +24,13 @@
#include <QTextCodec> #include <QTextCodec>
#include <QUrl> #include <QUrl>
#include "core/recordingnetworkaccessmanager.h"
using pb::tagreader::NetworkStatisticsResponse;
TagReaderWorker::TagReaderWorker(QIODevice* socket, QObject* parent) TagReaderWorker::TagReaderWorker(QIODevice* socket, QObject* parent)
: AbstractMessageHandler<pb::tagreader::Message>(socket, parent) {} : AbstractMessageHandler<pb::tagreader::Message>(socket, parent),
tag_reader_(new RecordingNetworkAccessManager) {}
void TagReaderWorker::MessageArrived(const pb::tagreader::Message& message) { void TagReaderWorker::MessageArrived(const pb::tagreader::Message& message) {
pb::tagreader::Message reply; pb::tagreader::Message reply;
@ -80,6 +85,8 @@ void TagReaderWorker::MessageArrived(const pb::tagreader::Message& message) {
reply.mutable_read_cloud_file_response()->clear_metadata(); reply.mutable_read_cloud_file_response()->clear_metadata();
} }
#endif #endif
} else if (message.has_network_statistics_request()) {
ReportNetworkStatistics(reply.mutable_network_statistics_response());
} }
SendReply(message, &reply); SendReply(message, &reply);
@ -90,3 +97,16 @@ void TagReaderWorker::DeviceClosed() {
qApp->exit(); qApp->exit();
} }
void TagReaderWorker::ReportNetworkStatistics(
NetworkStatisticsResponse* response) const {
QList<RecordingNetworkAccessManager::NetworkStat*> stats =
RecordingNetworkAccessManager::GetNetworkStatistics();
for (RecordingNetworkAccessManager::NetworkStat* stat : stats) {
NetworkStatisticsResponse::Entry* entry = response->add_entry();
entry->set_url(stat->url.toStdString());
entry->set_operation(
static_cast<NetworkStatisticsResponse::Operation>(stat->operation));
entry->set_bytes_received(stat->bytes_received);
}
}

View File

@ -32,6 +32,9 @@ class TagReaderWorker : public AbstractMessageHandler<pb::tagreader::Message> {
void DeviceClosed(); void DeviceClosed();
private: private:
void ReportNetworkStatistics(
pb::tagreader::NetworkStatisticsResponse* response) const;
TagReader tag_reader_; TagReader tag_reader_;
}; };

View File

@ -10,6 +10,7 @@ set(SOURCES
core/logging.cpp core/logging.cpp
core/messagehandler.cpp core/messagehandler.cpp
core/messagereply.cpp core/messagereply.cpp
core/recordingnetworkaccessmanager.cpp
core/waitforsignal.cpp core/waitforsignal.cpp
core/workerpool.cpp core/workerpool.cpp
) )
@ -18,6 +19,7 @@ set(HEADERS
core/closure.h core/closure.h
core/messagehandler.h core/messagehandler.h
core/messagereply.h core/messagereply.h
core/recordingnetworkaccessmanager.h
core/workerpool.h core/workerpool.h
) )

View File

@ -26,8 +26,9 @@ ClosureBase::ClosureBase(ObjectHelper* helper) : helper_(helper) {}
ClosureBase::~ClosureBase() {} ClosureBase::~ClosureBase() {}
CallbackClosure::CallbackClosure(QObject* sender, const char* signal, CallbackClosure::CallbackClosure(QObject* sender, const char* signal,
std::function<void()> callback) std::function<void()> callback,
: ClosureBase(new ObjectHelper(sender, signal, this)), bool permanent)
: ClosureBase(new ObjectHelper(sender, signal, this, permanent)),
callback_(callback) {} callback_(callback) {}
void CallbackClosure::Invoke() { callback_(); } void CallbackClosure::Invoke() { callback_(); }
@ -35,15 +36,16 @@ void CallbackClosure::Invoke() { callback_(); }
ObjectHelper* ClosureBase::helper() const { return helper_; } ObjectHelper* ClosureBase::helper() const { return helper_; }
ObjectHelper::ObjectHelper(QObject* sender, const char* signal, ObjectHelper::ObjectHelper(QObject* sender, const char* signal,
ClosureBase* closure) ClosureBase* closure, bool permanent)
: closure_(closure) { : closure_(closure),
permanent_(permanent) {
connect(sender, signal, SLOT(Invoked())); connect(sender, signal, SLOT(Invoked()));
connect(sender, SIGNAL(destroyed()), SLOT(deleteLater())); connect(sender, SIGNAL(destroyed()), SLOT(deleteLater()));
} }
void ObjectHelper::Invoked() { void ObjectHelper::Invoked() {
closure_->Invoke(); closure_->Invoke();
deleteLater(); if (!permanent_) deleteLater();
} }
void Unpack(QList<QGenericArgument>*) {} void Unpack(QList<QGenericArgument>*) {}
@ -55,6 +57,11 @@ _detail::ClosureBase* NewClosure(QObject* sender, const char* signal,
return new _detail::CallbackClosure(sender, signal, callback); return new _detail::CallbackClosure(sender, signal, callback);
} }
_detail::ClosureBase* NewPermanentClosure(QObject* sender, const char* signal,
std::function<void()> callback) {
return new _detail::CallbackClosure(sender, signal, callback, true);
}
void DoAfter(QObject* receiver, const char* slot, int msec) { void DoAfter(QObject* receiver, const char* slot, int msec) {
QTimer::singleShot(msec, receiver, slot); QTimer::singleShot(msec, receiver, slot);
} }

View File

@ -54,13 +54,15 @@ class ClosureBase {
class ObjectHelper : public QObject { class ObjectHelper : public QObject {
Q_OBJECT Q_OBJECT
public: public:
ObjectHelper(QObject* parent, const char* signal, ClosureBase* closure); ObjectHelper(QObject* parent, const char* signal, ClosureBase* closure,
bool permanent = false);
private slots: private slots:
void Invoked(); void Invoked();
private: private:
std::unique_ptr<ClosureBase> closure_; std::unique_ptr<ClosureBase> closure_;
bool permanent_;
Q_DISABLE_COPY(ObjectHelper); Q_DISABLE_COPY(ObjectHelper);
}; };
@ -139,7 +141,7 @@ class SharedClosure : public Closure<Args...> {
class CallbackClosure : public ClosureBase { class CallbackClosure : public ClosureBase {
public: public:
CallbackClosure(QObject* sender, const char* signal, CallbackClosure(QObject* sender, const char* signal,
std::function<void()> callback); std::function<void()> callback, bool permanent = false);
virtual void Invoke(); virtual void Invoke();
@ -168,6 +170,9 @@ _detail::ClosureBase* NewClosure(QSharedPointer<T> sender, const char* signal,
_detail::ClosureBase* NewClosure(QObject* sender, const char* signal, _detail::ClosureBase* NewClosure(QObject* sender, const char* signal,
std::function<void()> callback); std::function<void()> callback);
_detail::ClosureBase* NewPermanentClosure(QObject* sender, const char* signal,
std::function<void()> callback);
template <typename... Args> template <typename... Args>
_detail::ClosureBase* NewClosure(QObject* sender, const char* signal, _detail::ClosureBase* NewClosure(QObject* sender, const char* signal,
std::function<void(Args...)> callback, std::function<void(Args...)> callback,

View File

@ -39,7 +39,7 @@ class _MessageReplyBase : public QObject {
// Waits for the reply to finish by waiting on a semaphore. Never call this // Waits for the reply to finish by waiting on a semaphore. Never call this
// from the MessageHandler's thread or it will block forever. // from the MessageHandler's thread or it will block forever.
// Returns true if the call was successful. // Returns true if the call was successful.
bool WaitForFinished(); virtual bool WaitForFinished();
void Abort(); void Abort();
@ -64,7 +64,7 @@ class MessageReply : public _MessageReplyBase {
const MessageType& request_message() const { return request_message_; } const MessageType& request_message() const { return request_message_; }
const MessageType& message() const { return reply_message_; } const MessageType& message() const { return reply_message_; }
void SetReply(const MessageType& message); virtual void SetReply(const MessageType& message);
private: private:
MessageType request_message_; MessageType request_message_;

View File

@ -0,0 +1,54 @@
#include "recordingnetworkaccessmanager.h"
#include <QMutexLocker>
#include <QNetworkReply>
#include "core/closure.h"
#include "core/logging.h"
QMutex RecordingNetworkAccessManager::sMutex;
QList<RecordingNetworkAccessManager::NetworkStat*>*
RecordingNetworkAccessManager::sTrafficStats;
RecordingNetworkAccessManager::NetworkStat::NetworkStat(
QString url, QNetworkAccessManager::Operation op, int bytes_received)
: url(url), operation(op), bytes_received(bytes_received) {}
RecordingNetworkAccessManager::RecordingNetworkAccessManager(QObject* parent)
: QNetworkAccessManager(parent) {
StaticInit();
}
QNetworkReply* RecordingNetworkAccessManager::createRequest(
Operation op, const QNetworkRequest& req, QIODevice* data) {
QNetworkReply* reply = QNetworkAccessManager::createRequest(op, req, data);
NewClosure(
reply, SIGNAL(finished()), this, SLOT(Finished(QNetworkReply*)), reply);
// TODO: Listen to uploadProgress() too.
return reply;
}
void RecordingNetworkAccessManager::Finished(QNetworkReply* reply) const {
RecordRequest(*reply);
}
void RecordingNetworkAccessManager::StaticInit() {
QMutexLocker l(&sMutex);
if (sTrafficStats == nullptr) {
sTrafficStats = new QList<NetworkStat*>;
}
}
void RecordingNetworkAccessManager::RecordRequest(const QNetworkReply& reply) {
qLog(Debug) << "Recording request" << reply.request().url() << sTrafficStats;
QMutexLocker l(&sMutex);
sTrafficStats->append(new NetworkStat(
reply.request().url().toString(), reply.operation(), reply.size()));
qLog(Debug) << "Stats:" << sTrafficStats->count();
}
QList<RecordingNetworkAccessManager::NetworkStat*>
RecordingNetworkAccessManager::GetNetworkStatistics() {
QMutexLocker l(&sMutex);
return *sTrafficStats;
}

View File

@ -0,0 +1,55 @@
/* This file is part of Clementine.
Copyright 2015, John Maguire <john.maguire@gmail.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef RECORDINGNETWORKACCESSMANAGER_H
#define RECORDINGNETWORKACCESSMANAGER_H
#include <QMap>
#include <QMutex>
#include <QNetworkAccessManager>
class RecordingNetworkAccessManager : public QNetworkAccessManager {
Q_OBJECT
public:
explicit RecordingNetworkAccessManager(QObject* parent = nullptr);
struct NetworkStat {
QString url; // Stripped of params.
QNetworkAccessManager::Operation operation;
int bytes_received;
NetworkStat(QString, QNetworkAccessManager::Operation, int);
};
static QList<NetworkStat*> GetNetworkStatistics();
protected:
QNetworkReply* createRequest(
Operation op, const QNetworkRequest& req, QIODevice* data = 0) override;
private slots:
void Finished(QNetworkReply* reply) const;
private:
static void RecordRequest(const QNetworkReply& reply);
static void StaticInit();
static QMutex sMutex;
static QList<NetworkStat*>* sTrafficStats;
};
#endif // RECORDINGNETWORKACCESSMANAGER_H

View File

@ -21,6 +21,7 @@
#include <QAtomicInt> #include <QAtomicInt>
#include <QCoreApplication> #include <QCoreApplication>
#include <QFile> #include <QFile>
#include <QList>
#include <QLocalServer> #include <QLocalServer>
#include <QLocalSocket> #include <QLocalSocket>
#include <QMutex> #include <QMutex>
@ -88,6 +89,8 @@ class WorkerPool : public _WorkerPoolBase {
// worker. Can be called from any thread. // worker. Can be called from any thread.
ReplyType* SendMessageWithReply(MessageType* message); ReplyType* SendMessageWithReply(MessageType* message);
QList<ReplyType*> BroadcastMessageWithReply(MessageType* message);
protected: protected:
// These are all reimplemented slots, they are called on the WorkerPool's // These are all reimplemented slots, they are called on the WorkerPool's
// thread. // thread.
@ -141,6 +144,31 @@ class WorkerPool : public _WorkerPoolBase {
// my thread. // my thread.
HandlerType* NextHandler() const; HandlerType* NextHandler() const;
class BroadcastReply : public ReplyType {
public:
BroadcastReply(
const MessageType& request_message,
QList<ReplyType*> replies, QObject* parent = nullptr)
: ReplyType(request_message, parent),
replies_(replies) {
for (ReplyType* reply : replies_) {
NewClosure(reply, SIGNAL(Finished(bool)), [&]() {
int finished = 0;
for (ReplyType* reply : replies_) {
finished += reply->is_finished() ? 1 : 0;
}
if (finished == replies_.count()) {
emit this->Finished(true);
}
});
}
}
private:
QList<ReplyType*> replies_;
};
private: private:
QString local_server_name_; QString local_server_name_;
QString executable_name_; QString executable_name_;
@ -369,6 +397,19 @@ WorkerPool<HandlerType>::SendMessageWithReply(MessageType* message) {
return reply; return reply;
} }
template <typename HandlerType>
QList<typename WorkerPool<HandlerType>::ReplyType*>
WorkerPool<HandlerType>::BroadcastMessageWithReply(MessageType* message) {
QList<ReplyType*> replies;
for (const Worker& worker : workers_) {
ReplyType* reply = NewReply(message);
replies << reply;
worker.handler_->SendRequest(reply);
qLog(Debug) << "Sent request to worker: " << &worker;
}
return replies;
}
template <typename HandlerType> template <typename HandlerType>
void WorkerPool<HandlerType>::SendQueuedMessages() { void WorkerPool<HandlerType>::SendQueuedMessages() {
QMutexLocker l(&message_queue_mutex_); QMutexLocker l(&message_queue_mutex_);

View File

@ -116,9 +116,9 @@ const char* kASF_OriginalDate_ID = "WM/OriginalReleaseTime";
const char* kASF_OriginalYear_ID = "WM/OriginalReleaseYear"; const char* kASF_OriginalYear_ID = "WM/OriginalReleaseYear";
} }
TagReader::TagReader() TagReader::TagReader(QNetworkAccessManager* network)
: factory_(new TagLibFileRefFactory), : factory_(new TagLibFileRefFactory),
network_(new QNetworkAccessManager), network_(network ? network : new QNetworkAccessManager),
kEmbeddedCover("(embedded)") {} kEmbeddedCover("(embedded)") {}
void TagReader::ReadFile(const QString& filename, void TagReader::ReadFile(const QString& filename,
@ -1072,7 +1072,7 @@ bool TagReader::ReadCloudFile(const QUrl& download_url, const QString& title,
qLog(Debug) << "Loading tags from" << title; qLog(Debug) << "Loading tags from" << title;
std::unique_ptr<CloudStream> stream(new CloudStream( std::unique_ptr<CloudStream> stream(new CloudStream(
download_url, title, size, authorisation_header, network_)); download_url, title, size, authorisation_header, network_.get()));
stream->Precache(); stream->Precache();
std::unique_ptr<TagLib::File> tag; std::unique_ptr<TagLib::File> tag;
if (mime_type == "audio/mpeg" && title.endsWith(".mp3")) { if (mime_type == "audio/mpeg" && title.endsWith(".mp3")) {

View File

@ -18,7 +18,10 @@
#ifndef TAGREADER_H #ifndef TAGREADER_H
#define TAGREADER_H #define TAGREADER_H
#include <memory>
#include <QByteArray> #include <QByteArray>
#include <QNetworkAccessManager>
#include <taglib/xiphcomment.h> #include <taglib/xiphcomment.h>
@ -49,7 +52,7 @@ class FileRefFactory;
*/ */
class TagReader { class TagReader {
public: public:
TagReader(); TagReader(QNetworkAccessManager* network = 0);
void ReadFile(const QString& filename, void ReadFile(const QString& filename,
pb::tagreader::SongMetadata* song) const; pb::tagreader::SongMetadata* song) const;
@ -120,7 +123,7 @@ class TagReader {
TagLib::ID3v2::Tag* tag); TagLib::ID3v2::Tag* tag);
FileRefFactory* factory_; FileRefFactory* factory_;
QNetworkAccessManager* network_; std::unique_ptr<QNetworkAccessManager> network_;
const std::string kEmbeddedCover; const std::string kEmbeddedCover;
}; };

View File

@ -118,6 +118,30 @@ message SaveSongRatingToFileResponse {
optional bool success = 1; optional bool success = 1;
} }
message NetworkStatisticsRequest {
}
message NetworkStatisticsResponse {
// This should match QNetworkAccessManager::Operation.
enum Operation {
UNKNOWN = 0;
HEAD = 1;
GET = 2;
PUT = 3;
POST = 4;
DELETE = 5;
CUSTOM = 6;
}
message Entry {
optional string url = 1;
optional Operation operation = 2;
optional int32 bytes_sent = 3;
optional int32 bytes_received = 4;
}
repeated Entry entry = 1;
}
message Message { message Message {
optional int32 id = 1; optional int32 id = 1;
@ -141,4 +165,7 @@ message Message {
optional SaveSongRatingToFileRequest save_song_rating_to_file_request = 14; optional SaveSongRatingToFileRequest save_song_rating_to_file_request = 14;
optional SaveSongRatingToFileResponse save_song_rating_to_file_response = 15; optional SaveSongRatingToFileResponse save_song_rating_to_file_response = 15;
optional NetworkStatisticsRequest network_statistics_request = 16;
optional NetworkStatisticsResponse network_statistics_response = 17;
} }

View File

@ -1197,6 +1197,11 @@ optional_source(LINUX
HEADERS core/ubuntuunityhack.h HEADERS core/ubuntuunityhack.h
) )
optional_source(LINUX
SOURCES core/signalhandler.cpp
HEADERS core/signalhandler.h
)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in
${CMAKE_CURRENT_BINARY_DIR}/config.h) ${CMAKE_CURRENT_BINARY_DIR}/config.h)

View File

@ -26,11 +26,15 @@
#include <QNetworkReply> #include <QNetworkReply>
#include "core/closure.h" #include "core/closure.h"
#include "utilities.h" #include "core/logging.h"
#include "core/utilities.h"
QMutex ThreadSafeNetworkDiskCache::sMutex; QMutex ThreadSafeNetworkDiskCache::sMutex;
QNetworkDiskCache* ThreadSafeNetworkDiskCache::sCache = nullptr; QNetworkDiskCache* ThreadSafeNetworkDiskCache::sCache = nullptr;
QMutex NetworkAccessManager::sMutex;
QMap<QString, int>* NetworkAccessManager::sTrafficAnalysis = nullptr;
ThreadSafeNetworkDiskCache::ThreadSafeNetworkDiskCache(QObject* parent) { ThreadSafeNetworkDiskCache::ThreadSafeNetworkDiskCache(QObject* parent) {
QMutexLocker l(&sMutex); QMutexLocker l(&sMutex);
if (!sCache) { if (!sCache) {
@ -84,9 +88,23 @@ void ThreadSafeNetworkDiskCache::clear() {
NetworkAccessManager::NetworkAccessManager(QObject* parent) NetworkAccessManager::NetworkAccessManager(QObject* parent)
: QNetworkAccessManager(parent) { : QNetworkAccessManager(parent) {
StaticInit();
setCache(new ThreadSafeNetworkDiskCache(this)); setCache(new ThreadSafeNetworkDiskCache(this));
} }
void NetworkAccessManager::StaticInit() {
QMutexLocker l(&sMutex);
if (sTrafficAnalysis == nullptr) {
sTrafficAnalysis = new QMap<QString, int>;
}
}
void NetworkAccessManager::PrintNetworkStatistics() {
for (const auto& it : sTrafficAnalysis->toStdMap()) {
qLog(Debug) << it.first << it.second;
}
}
QNetworkReply* NetworkAccessManager::createRequest( QNetworkReply* NetworkAccessManager::createRequest(
Operation op, const QNetworkRequest& request, QIODevice* outgoingData) { Operation op, const QNetworkRequest& request, QIODevice* outgoingData) {
QByteArray user_agent = QString("%1 %2") QByteArray user_agent = QString("%1 %2")
@ -116,9 +134,28 @@ QNetworkReply* NetworkAccessManager::createRequest(
QNetworkRequest::PreferCache); QNetworkRequest::PreferCache);
} }
RecordRequest(new_request);
return QNetworkAccessManager::createRequest(op, new_request, outgoingData); return QNetworkAccessManager::createRequest(op, new_request, outgoingData);
} }
void NetworkAccessManager::RecordRequest(const QNetworkRequest& request) {
QMutexLocker l(&sMutex);
if (sTrafficAnalysis == nullptr) return;
const QUrl& url = request.url();
const QString recorded = QString("%1://%2:%3%4")
.arg(url.scheme())
.arg(url.host())
.arg(url.port())
.arg(url.path());
if (sTrafficAnalysis->contains(recorded)) {
++(*sTrafficAnalysis)[recorded];
} else {
(*sTrafficAnalysis)[recorded] = 1;
}
}
NetworkTimeouts::NetworkTimeouts(int timeout_msec, QObject* parent) NetworkTimeouts::NetworkTimeouts(int timeout_msec, QObject* parent)
: timeout_msec_(timeout_msec) {} : timeout_msec_(timeout_msec) {}

View File

@ -21,6 +21,7 @@
#define CORE_NETWORK_H_ #define CORE_NETWORK_H_
#include <QAbstractNetworkCache> #include <QAbstractNetworkCache>
#include <QMap>
#include <QMutex> #include <QMutex>
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include <QNetworkReply> #include <QNetworkReply>
@ -55,6 +56,14 @@ class NetworkAccessManager : public QNetworkAccessManager {
protected: protected:
QNetworkReply* createRequest(Operation op, const QNetworkRequest& request, QNetworkReply* createRequest(Operation op, const QNetworkRequest& request,
QIODevice* outgoingData); QIODevice* outgoingData);
private:
static void RecordRequest(const QNetworkRequest& request);
static void StaticInit();
static void PrintNetworkStatistics();
static QMutex sMutex;
static QMap<QString, int>* sTrafficAnalysis;
}; };
class RedirectFollower : public QObject { class RedirectFollower : public QObject {

View File

@ -0,0 +1,54 @@
#include "core/signalhandler.h"
#include <assert.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <sys/signalfd.h>
#include <unistd.h>
#include <QByteArray>
#include <QSocketNotifier>
#include "core/logging.h"
SignalHandler::SignalHandler(QObject* parent)
: QObject(parent) {
sigset_t mask;
memset(&mask, 0, sizeof(mask));
sigemptyset(&mask);
sigaddset(&mask, SIGUSR1);
// Listen to the handled signals in a file descriptor.
const int fd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC);
if (fd == -1) {
perror(Q_FUNC_INFO);
qLog(Fatal) << "Failed to register signal handler with signalfd()";
}
notifier_ = new QSocketNotifier(fd, QSocketNotifier::Read, this);
connect(notifier_, SIGNAL(activated(int)), SLOT(SignalReceived()));
}
void SignalHandler::SignalReceived() {
qLog(Debug) << Q_FUNC_INFO;
QByteArray buffer(sizeof(signalfd_siginfo), 0);
// read() should fail with EAGAIN if there are no more signals.
forever {
int bytes_read = read(notifier_->socket(), buffer.data(), buffer.size());
if (bytes_read == buffer.size()) {
signalfd_siginfo* siginfo =
reinterpret_cast<signalfd_siginfo*>(buffer.data());
qLog(Debug) << Q_FUNC_INFO
<< siginfo->ssi_signo << strsignal(siginfo->ssi_signo);;
switch (siginfo->ssi_signo) {
case SIGUSR1:
emit SIG_USR1();
break;
// Add more signals here.
default:
break;
}
} else if (bytes_read <= 0) {
break;
}
}
}

23
src/core/signalhandler.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef CORE_SIGNALHANDLER_H
#define CORE_SIGNALHANDLER_H
#include <QObject>
class QSocketNotifier;
class SignalHandler : public QObject {
Q_OBJECT
public:
explicit SignalHandler(QObject* parent = 0);
signals:
void SIG_USR1();
private slots:
void SignalReceived();
private:
QSocketNotifier* notifier_;
};
#endif // CORE_SIGNALHANDLER_H

View File

@ -27,6 +27,10 @@
#include <QThread> #include <QThread>
#include <QUrl> #include <QUrl>
#include "core/waitforsignal.h"
using pb::tagreader::NetworkStatisticsResponse;
const char* TagReaderClient::kWorkerExecutableName = "clementine-tagreader"; const char* TagReaderClient::kWorkerExecutableName = "clementine-tagreader";
TagReaderClient* TagReaderClient::sInstance = nullptr; TagReaderClient* TagReaderClient::sInstance = nullptr;
@ -124,6 +128,43 @@ TagReaderReply* TagReaderClient::LoadEmbeddedArt(const QString& filename) {
return worker_pool_->SendMessageWithReply(&message); return worker_pool_->SendMessageWithReply(&message);
} }
class BroadcastReply : public TagReaderReply {
public:
BroadcastReply(
const pb::tagreader::Message& request_message,
const QList<TagReaderReply*>& replies,
QObject* parent = nullptr)
: TagReaderReply(request_message, parent),
replies_(replies) {
for (TagReaderReply* reply : replies_) {
NewClosure(reply, SIGNAL(Finished(bool)), [=]() {
if (IsFinished()) {
int successes = std::count_if(
replies_.constBegin(), replies_.constEnd(),
std::mem_fn(&TagReaderReply::is_successful));
emit this->Finished(successes == replies_.count());
}
});
}
}
const QList<TagReaderReply*>& replies() const { return replies_; }
bool WaitForFinished() override {
WaitForSignal(this, SIGNAL(Finished(bool)));
return IsFinished();
}
private:
bool IsFinished() const {
int finished = std::count_if(
replies_.constBegin(), replies_.constEnd(),
std::mem_fn(&TagReaderReply::is_finished));
return finished == replies_.count();
}
QList<TagReaderReply*> replies_;
};
TagReaderReply* TagReaderClient::ReadCloudFile( TagReaderReply* TagReaderClient::ReadCloudFile(
const QUrl& download_url, const QString& title, int size, const QUrl& download_url, const QString& title, int size,
const QString& mime_type, const QString& authorisation_header) { const QString& mime_type, const QString& authorisation_header) {
@ -141,6 +182,45 @@ TagReaderReply* TagReaderClient::ReadCloudFile(
return worker_pool_->SendMessageWithReply(&message); return worker_pool_->SendMessageWithReply(&message);
} }
TagReaderReply* TagReaderClient::GetNetworkStatistics() {
pb::tagreader::Message message;
message.mutable_network_statistics_request();
QList<TagReaderReply*> replies =
worker_pool_->BroadcastMessageWithReply(&message);
return new BroadcastReply(message, replies);
}
void TagReaderClient::GetNetworkStatisticsBlocking() {
BroadcastReply* reply = static_cast<BroadcastReply*>(GetNetworkStatistics());
reply->WaitForFinished();
reply->deleteLater();
NetworkStatisticsResponse response;
for (TagReaderReply* r : reply->replies()) {
response.MergeFrom(r->message().network_statistics_response());
}
// Aggregate stats.
QMap<QString, int> requests_by_host;
QMap<QString, qint64> bytes_received_by_host;
for (const NetworkStatisticsResponse::Entry& entry : response.entry()) {
QUrl url(QStringFromStdString(entry.url()));
QString host = url.authority();
if (requests_by_host.contains(host)) {
++requests_by_host[host];
} else {
requests_by_host[host] = 1;
}
if (bytes_received_by_host.contains(host)) {
bytes_received_by_host[host] += entry.bytes_received();
} else {
bytes_received_by_host[host] = entry.bytes_received();
}
}
qLog(Debug) << requests_by_host;
qLog(Debug) << bytes_received_by_host;
}
void TagReaderClient::ReadFileBlocking(const QString& filename, Song* song) { void TagReaderClient::ReadFileBlocking(const QString& filename, Song* song) {
Q_ASSERT(QThread::currentThread() != thread()); Q_ASSERT(QThread::currentThread() != thread());

View File

@ -53,6 +53,7 @@ class TagReaderClient : public QObject {
ReplyType* ReadCloudFile(const QUrl& download_url, const QString& title, ReplyType* ReadCloudFile(const QUrl& download_url, const QString& title,
int size, const QString& mime_type, int size, const QString& mime_type,
const QString& authorisation_header); const QString& authorisation_header);
ReplyType* GetNetworkStatistics();
// Convenience functions that call the above functions and wait for a // Convenience functions that call the above functions and wait for a
// response. These block the calling thread with a semaphore, and must NOT // response. These block the calling thread with a semaphore, and must NOT
@ -63,6 +64,7 @@ class TagReaderClient : public QObject {
bool UpdateSongRatingBlocking(const Song& metadata); bool UpdateSongRatingBlocking(const Song& metadata);
bool IsMediaFileBlocking(const QString& filename); bool IsMediaFileBlocking(const QString& filename);
QImage LoadEmbeddedArtBlocking(const QString& filename); QImage LoadEmbeddedArtBlocking(const QString& filename);
void GetNetworkStatisticsBlocking();
// TODO(David Sansome): Make this not a singleton // TODO(David Sansome): Make this not a singleton
static TagReaderClient* Instance() { return sInstance; } static TagReaderClient* Instance() { return sInstance; }

View File

@ -16,6 +16,7 @@
*/ */
#include <memory> #include <memory>
#include <signal.h>
#include <QtGlobal> #include <QtGlobal>
@ -83,6 +84,10 @@
#include <sys/sysctl.h> #include <sys/sysctl.h>
#endif #endif
#ifdef Q_OS_LINUX
#include "core/signalhandler.h"
#endif
#ifdef HAVE_LIBLASTFM #ifdef HAVE_LIBLASTFM
#include "internet/lastfm/lastfmservice.h" #include "internet/lastfm/lastfmservice.h"
#else #else
@ -250,6 +255,15 @@ void ScanGIOModulePath() {
#endif // HAVE_GIO #endif // HAVE_GIO
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
#ifdef Q_OS_LINUX
sigset_t blocked_signals;
memset(&blocked_signals, 0, sizeof(blocked_signals));
sigemptyset(&blocked_signals);
sigaddset(&blocked_signals, SIGUSR1);
// This must be done before any other threads are spawned.
pthread_sigmask(SIG_BLOCK, &blocked_signals, nullptr);
#endif
if (CrashReporting::SendCrashReport(argc, argv)) { if (CrashReporting::SendCrashReport(argc, argv)) {
return 0; return 0;
} }
@ -453,6 +467,16 @@ int main(int argc, char* argv[]) {
app.cover_providers()->AddProvider(new AmazonCoverProvider); app.cover_providers()->AddProvider(new AmazonCoverProvider);
app.cover_providers()->AddProvider(new MusicbrainzCoverProvider); app.cover_providers()->AddProvider(new MusicbrainzCoverProvider);
#ifdef Q_OS_LINUX
SignalHandler signal_handler;
NewPermanentClosure(&signal_handler, SIGNAL(SIG_USR1()), [&app]() {
qLog(Debug) << "Received SIGUSR1"
<< "thread: " << QThread::currentThread()
<< "main thread: " << qApp->thread();
app.tag_reader_client()->GetNetworkStatisticsBlocking();
});
#endif
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
// In 11.04 Ubuntu decided that the system tray should be reserved for certain // In 11.04 Ubuntu decided that the system tray should be reserved for certain
// whitelisted applications. Clementine will override this setting and insert // whitelisted applications. Clementine will override this setting and insert

View File

@ -846,7 +846,7 @@ MainWindow::MainWindow(Application* app, SystemTrayIcon* tray_icon, OSD* osd,
connect(ui_->action_kittens, SIGNAL(toggled(bool)), app_->network_remote(), connect(ui_->action_kittens, SIGNAL(toggled(bool)), app_->network_remote(),
SLOT(EnableKittens(bool))); SLOT(EnableKittens(bool)));
// Hide the console // Hide the console
// connect(ui_->action_console, SIGNAL(triggered()), SLOT(ShowConsole())); connect(ui_->action_console, SIGNAL(triggered()), SLOT(ShowConsole()));
NowPlayingWidgetPositionChanged(ui_->now_playing->show_above_status_bar()); NowPlayingWidgetPositionChanged(ui_->now_playing->show_above_status_bar());
// Load theme // Load theme

View File

@ -461,6 +461,7 @@
<addaction name="action_hypnotoad"/> <addaction name="action_hypnotoad"/>
<addaction name="action_enterprise"/> <addaction name="action_enterprise"/>
<addaction name="action_kittens"/> <addaction name="action_kittens"/>
<addaction name="action_console"/>
<addaction name="separator"/> <addaction name="separator"/>
</widget> </widget>
<widget class="QMenu" name="menu_tools"> <widget class="QMenu" name="menu_tools">