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 <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);
}
}

View File

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

View File

@ -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
)

View File

@ -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);
}

View File

@ -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,

View File

@ -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_;

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 <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_);

View File

@ -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")) {

View File

@ -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;
};

View File

@ -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;
}

View File

@ -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)

View File

@ -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) {}

View File

@ -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 {

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 <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());

View File

@ -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; }

View File

@ -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

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(),
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

View File

@ -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">