WIP adding support for tracking network requests.
This commit is contained in:
parent
daddbdea96
commit
c1e2753c9d
|
@ -24,8 +24,13 @@
|
|||
#include <QTextCodec>
|
||||
#include <QUrl>
|
||||
|
||||
#include "core/recordingnetworkaccessmanager.h"
|
||||
|
||||
using pb::tagreader::NetworkStatisticsResponse;
|
||||
|
||||
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) {
|
||||
pb::tagreader::Message reply;
|
||||
|
@ -80,6 +85,8 @@ void TagReaderWorker::MessageArrived(const pb::tagreader::Message& message) {
|
|||
reply.mutable_read_cloud_file_response()->clear_metadata();
|
||||
}
|
||||
#endif
|
||||
} else if (message.has_network_statistics_request()) {
|
||||
ReportNetworkStatistics(reply.mutable_network_statistics_response());
|
||||
}
|
||||
|
||||
SendReply(message, &reply);
|
||||
|
@ -90,3 +97,16 @@ void TagReaderWorker::DeviceClosed() {
|
|||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,9 @@ class TagReaderWorker : public AbstractMessageHandler<pb::tagreader::Message> {
|
|||
void DeviceClosed();
|
||||
|
||||
private:
|
||||
void ReportNetworkStatistics(
|
||||
pb::tagreader::NetworkStatisticsResponse* response) const;
|
||||
|
||||
TagReader tag_reader_;
|
||||
};
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ set(SOURCES
|
|||
core/logging.cpp
|
||||
core/messagehandler.cpp
|
||||
core/messagereply.cpp
|
||||
core/recordingnetworkaccessmanager.cpp
|
||||
core/waitforsignal.cpp
|
||||
core/workerpool.cpp
|
||||
)
|
||||
|
@ -18,6 +19,7 @@ set(HEADERS
|
|||
core/closure.h
|
||||
core/messagehandler.h
|
||||
core/messagereply.h
|
||||
core/recordingnetworkaccessmanager.h
|
||||
core/workerpool.h
|
||||
)
|
||||
|
||||
|
|
|
@ -26,8 +26,9 @@ ClosureBase::ClosureBase(ObjectHelper* helper) : helper_(helper) {}
|
|||
ClosureBase::~ClosureBase() {}
|
||||
|
||||
CallbackClosure::CallbackClosure(QObject* sender, const char* signal,
|
||||
std::function<void()> callback)
|
||||
: ClosureBase(new ObjectHelper(sender, signal, this)),
|
||||
std::function<void()> callback,
|
||||
bool permanent)
|
||||
: ClosureBase(new ObjectHelper(sender, signal, this, permanent)),
|
||||
callback_(callback) {}
|
||||
|
||||
void CallbackClosure::Invoke() { callback_(); }
|
||||
|
@ -35,15 +36,16 @@ void CallbackClosure::Invoke() { callback_(); }
|
|||
ObjectHelper* ClosureBase::helper() const { return helper_; }
|
||||
|
||||
ObjectHelper::ObjectHelper(QObject* sender, const char* signal,
|
||||
ClosureBase* closure)
|
||||
: closure_(closure) {
|
||||
ClosureBase* closure, bool permanent)
|
||||
: closure_(closure),
|
||||
permanent_(permanent) {
|
||||
connect(sender, signal, SLOT(Invoked()));
|
||||
connect(sender, SIGNAL(destroyed()), SLOT(deleteLater()));
|
||||
}
|
||||
|
||||
void ObjectHelper::Invoked() {
|
||||
closure_->Invoke();
|
||||
deleteLater();
|
||||
if (!permanent_) deleteLater();
|
||||
}
|
||||
|
||||
void Unpack(QList<QGenericArgument>*) {}
|
||||
|
@ -55,6 +57,11 @@ _detail::ClosureBase* NewClosure(QObject* sender, const char* signal,
|
|||
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) {
|
||||
QTimer::singleShot(msec, receiver, slot);
|
||||
}
|
||||
|
|
|
@ -54,13 +54,15 @@ class ClosureBase {
|
|||
class ObjectHelper : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ObjectHelper(QObject* parent, const char* signal, ClosureBase* closure);
|
||||
ObjectHelper(QObject* parent, const char* signal, ClosureBase* closure,
|
||||
bool permanent = false);
|
||||
|
||||
private slots:
|
||||
void Invoked();
|
||||
|
||||
private:
|
||||
std::unique_ptr<ClosureBase> closure_;
|
||||
bool permanent_;
|
||||
Q_DISABLE_COPY(ObjectHelper);
|
||||
};
|
||||
|
||||
|
@ -139,7 +141,7 @@ class SharedClosure : public Closure<Args...> {
|
|||
class CallbackClosure : public ClosureBase {
|
||||
public:
|
||||
CallbackClosure(QObject* sender, const char* signal,
|
||||
std::function<void()> callback);
|
||||
std::function<void()> callback, bool permanent = false);
|
||||
|
||||
virtual void Invoke();
|
||||
|
||||
|
@ -168,6 +170,9 @@ _detail::ClosureBase* NewClosure(QSharedPointer<T> sender, const char* signal,
|
|||
_detail::ClosureBase* NewClosure(QObject* sender, const char* signal,
|
||||
std::function<void()> callback);
|
||||
|
||||
_detail::ClosureBase* NewPermanentClosure(QObject* sender, const char* signal,
|
||||
std::function<void()> callback);
|
||||
|
||||
template <typename... Args>
|
||||
_detail::ClosureBase* NewClosure(QObject* sender, const char* signal,
|
||||
std::function<void(Args...)> callback,
|
||||
|
|
|
@ -39,7 +39,7 @@ class _MessageReplyBase : public QObject {
|
|||
// Waits for the reply to finish by waiting on a semaphore. Never call this
|
||||
// from the MessageHandler's thread or it will block forever.
|
||||
// Returns true if the call was successful.
|
||||
bool WaitForFinished();
|
||||
virtual bool WaitForFinished();
|
||||
|
||||
void Abort();
|
||||
|
||||
|
@ -64,7 +64,7 @@ class MessageReply : public _MessageReplyBase {
|
|||
const MessageType& request_message() const { return request_message_; }
|
||||
const MessageType& message() const { return reply_message_; }
|
||||
|
||||
void SetReply(const MessageType& message);
|
||||
virtual void SetReply(const MessageType& message);
|
||||
|
||||
private:
|
||||
MessageType request_message_;
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -21,6 +21,7 @@
|
|||
#include <QAtomicInt>
|
||||
#include <QCoreApplication>
|
||||
#include <QFile>
|
||||
#include <QList>
|
||||
#include <QLocalServer>
|
||||
#include <QLocalSocket>
|
||||
#include <QMutex>
|
||||
|
@ -88,6 +89,8 @@ class WorkerPool : public _WorkerPoolBase {
|
|||
// worker. Can be called from any thread.
|
||||
ReplyType* SendMessageWithReply(MessageType* message);
|
||||
|
||||
QList<ReplyType*> BroadcastMessageWithReply(MessageType* message);
|
||||
|
||||
protected:
|
||||
// These are all reimplemented slots, they are called on the WorkerPool's
|
||||
// thread.
|
||||
|
@ -141,6 +144,31 @@ class WorkerPool : public _WorkerPoolBase {
|
|||
// my thread.
|
||||
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:
|
||||
QString local_server_name_;
|
||||
QString executable_name_;
|
||||
|
@ -369,6 +397,19 @@ WorkerPool<HandlerType>::SendMessageWithReply(MessageType* message) {
|
|||
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>
|
||||
void WorkerPool<HandlerType>::SendQueuedMessages() {
|
||||
QMutexLocker l(&message_queue_mutex_);
|
||||
|
|
|
@ -116,9 +116,9 @@ const char* kASF_OriginalDate_ID = "WM/OriginalReleaseTime";
|
|||
const char* kASF_OriginalYear_ID = "WM/OriginalReleaseYear";
|
||||
}
|
||||
|
||||
TagReader::TagReader()
|
||||
TagReader::TagReader(QNetworkAccessManager* network)
|
||||
: factory_(new TagLibFileRefFactory),
|
||||
network_(new QNetworkAccessManager),
|
||||
network_(network ? network : new QNetworkAccessManager),
|
||||
kEmbeddedCover("(embedded)") {}
|
||||
|
||||
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;
|
||||
|
||||
std::unique_ptr<CloudStream> stream(new CloudStream(
|
||||
download_url, title, size, authorisation_header, network_));
|
||||
download_url, title, size, authorisation_header, network_.get()));
|
||||
stream->Precache();
|
||||
std::unique_ptr<TagLib::File> tag;
|
||||
if (mime_type == "audio/mpeg" && title.endsWith(".mp3")) {
|
||||
|
|
|
@ -18,7 +18,10 @@
|
|||
#ifndef TAGREADER_H
|
||||
#define TAGREADER_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QNetworkAccessManager>
|
||||
|
||||
#include <taglib/xiphcomment.h>
|
||||
|
||||
|
@ -49,7 +52,7 @@ class FileRefFactory;
|
|||
*/
|
||||
class TagReader {
|
||||
public:
|
||||
TagReader();
|
||||
TagReader(QNetworkAccessManager* network = 0);
|
||||
|
||||
void ReadFile(const QString& filename,
|
||||
pb::tagreader::SongMetadata* song) const;
|
||||
|
@ -120,7 +123,7 @@ class TagReader {
|
|||
TagLib::ID3v2::Tag* tag);
|
||||
|
||||
FileRefFactory* factory_;
|
||||
QNetworkAccessManager* network_;
|
||||
std::unique_ptr<QNetworkAccessManager> network_;
|
||||
|
||||
const std::string kEmbeddedCover;
|
||||
};
|
||||
|
|
|
@ -118,6 +118,30 @@ message SaveSongRatingToFileResponse {
|
|||
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 {
|
||||
optional int32 id = 1;
|
||||
|
||||
|
@ -135,10 +159,13 @@ message Message {
|
|||
|
||||
optional ReadCloudFileRequest read_cloud_file_request = 10;
|
||||
optional ReadCloudFileResponse read_cloud_file_response = 11;
|
||||
|
||||
|
||||
optional SaveSongStatisticsToFileRequest save_song_statistics_to_file_request = 12;
|
||||
optional SaveSongStatisticsToFileResponse save_song_statistics_to_file_response = 13;
|
||||
|
||||
|
||||
optional SaveSongRatingToFileRequest save_song_rating_to_file_request = 14;
|
||||
optional SaveSongRatingToFileResponse save_song_rating_to_file_response = 15;
|
||||
|
||||
optional NetworkStatisticsRequest network_statistics_request = 16;
|
||||
optional NetworkStatisticsResponse network_statistics_response = 17;
|
||||
}
|
||||
|
|
|
@ -1197,6 +1197,11 @@ optional_source(LINUX
|
|||
HEADERS core/ubuntuunityhack.h
|
||||
)
|
||||
|
||||
optional_source(LINUX
|
||||
SOURCES core/signalhandler.cpp
|
||||
HEADERS core/signalhandler.h
|
||||
)
|
||||
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/config.h)
|
||||
|
||||
|
|
|
@ -26,11 +26,15 @@
|
|||
#include <QNetworkReply>
|
||||
|
||||
#include "core/closure.h"
|
||||
#include "utilities.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/utilities.h"
|
||||
|
||||
QMutex ThreadSafeNetworkDiskCache::sMutex;
|
||||
QNetworkDiskCache* ThreadSafeNetworkDiskCache::sCache = nullptr;
|
||||
|
||||
QMutex NetworkAccessManager::sMutex;
|
||||
QMap<QString, int>* NetworkAccessManager::sTrafficAnalysis = nullptr;
|
||||
|
||||
ThreadSafeNetworkDiskCache::ThreadSafeNetworkDiskCache(QObject* parent) {
|
||||
QMutexLocker l(&sMutex);
|
||||
if (!sCache) {
|
||||
|
@ -84,9 +88,23 @@ void ThreadSafeNetworkDiskCache::clear() {
|
|||
|
||||
NetworkAccessManager::NetworkAccessManager(QObject* parent)
|
||||
: QNetworkAccessManager(parent) {
|
||||
StaticInit();
|
||||
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(
|
||||
Operation op, const QNetworkRequest& request, QIODevice* outgoingData) {
|
||||
QByteArray user_agent = QString("%1 %2")
|
||||
|
@ -116,9 +134,28 @@ QNetworkReply* NetworkAccessManager::createRequest(
|
|||
QNetworkRequest::PreferCache);
|
||||
}
|
||||
|
||||
RecordRequest(new_request);
|
||||
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)
|
||||
: timeout_msec_(timeout_msec) {}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#define CORE_NETWORK_H_
|
||||
|
||||
#include <QAbstractNetworkCache>
|
||||
#include <QMap>
|
||||
#include <QMutex>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
|
@ -55,6 +56,14 @@ class NetworkAccessManager : public QNetworkAccessManager {
|
|||
protected:
|
||||
QNetworkReply* createRequest(Operation op, const QNetworkRequest& request,
|
||||
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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -27,6 +27,10 @@
|
|||
#include <QThread>
|
||||
#include <QUrl>
|
||||
|
||||
#include "core/waitforsignal.h"
|
||||
|
||||
using pb::tagreader::NetworkStatisticsResponse;
|
||||
|
||||
const char* TagReaderClient::kWorkerExecutableName = "clementine-tagreader";
|
||||
TagReaderClient* TagReaderClient::sInstance = nullptr;
|
||||
|
||||
|
@ -124,6 +128,43 @@ TagReaderReply* TagReaderClient::LoadEmbeddedArt(const QString& filename) {
|
|||
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(
|
||||
const QUrl& download_url, const QString& title, int size,
|
||||
const QString& mime_type, const QString& authorisation_header) {
|
||||
|
@ -141,6 +182,45 @@ TagReaderReply* TagReaderClient::ReadCloudFile(
|
|||
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) {
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ class TagReaderClient : public QObject {
|
|||
ReplyType* ReadCloudFile(const QUrl& download_url, const QString& title,
|
||||
int size, const QString& mime_type,
|
||||
const QString& authorisation_header);
|
||||
ReplyType* GetNetworkStatistics();
|
||||
|
||||
// Convenience functions that call the above functions and wait for a
|
||||
// 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 IsMediaFileBlocking(const QString& filename);
|
||||
QImage LoadEmbeddedArtBlocking(const QString& filename);
|
||||
void GetNetworkStatisticsBlocking();
|
||||
|
||||
// TODO(David Sansome): Make this not a singleton
|
||||
static TagReaderClient* Instance() { return sInstance; }
|
||||
|
|
24
src/main.cpp
24
src/main.cpp
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
|
||||
#include <memory>
|
||||
#include <signal.h>
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
|
@ -83,6 +84,10 @@
|
|||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
#include "core/signalhandler.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBLASTFM
|
||||
#include "internet/lastfm/lastfmservice.h"
|
||||
#else
|
||||
|
@ -250,6 +255,15 @@ void ScanGIOModulePath() {
|
|||
#endif // HAVE_GIO
|
||||
|
||||
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)) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -453,6 +467,16 @@ int main(int argc, char* argv[]) {
|
|||
app.cover_providers()->AddProvider(new AmazonCoverProvider);
|
||||
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
|
||||
// In 11.04 Ubuntu decided that the system tray should be reserved for certain
|
||||
// whitelisted applications. Clementine will override this setting and insert
|
||||
|
|
|
@ -846,7 +846,7 @@ MainWindow::MainWindow(Application* app, SystemTrayIcon* tray_icon, OSD* osd,
|
|||
connect(ui_->action_kittens, SIGNAL(toggled(bool)), app_->network_remote(),
|
||||
SLOT(EnableKittens(bool)));
|
||||
// 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());
|
||||
|
||||
// Load theme
|
||||
|
|
|
@ -461,6 +461,7 @@
|
|||
<addaction name="action_hypnotoad"/>
|
||||
<addaction name="action_enterprise"/>
|
||||
<addaction name="action_kittens"/>
|
||||
<addaction name="action_console"/>
|
||||
<addaction name="separator"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menu_tools">
|
||||
|
|
Loading…
Reference in New Issue