Keep track of some statistics while searching for album covers, and show a statistics dialog at the end. Fixes issue 1921

This commit is contained in:
David Sansome 2011-06-26 15:07:48 +00:00
parent 42801a967b
commit 7773e98ebf
18 changed files with 464 additions and 54 deletions

View File

@ -100,6 +100,8 @@ set(SOURCES
covers/artloader.cpp covers/artloader.cpp
covers/coverprovider.cpp covers/coverprovider.cpp
covers/coverproviders.cpp covers/coverproviders.cpp
covers/coversearchstatistics.cpp
covers/coversearchstatisticsdialog.cpp
covers/kittenloader.cpp covers/kittenloader.cpp
devices/connecteddevice.cpp devices/connecteddevice.cpp
@ -329,6 +331,7 @@ set(HEADERS
covers/artloader.h covers/artloader.h
covers/coverprovider.h covers/coverprovider.h
covers/coverproviders.h covers/coverproviders.h
covers/coversearchstatisticsdialog.h
covers/kittenloader.h covers/kittenloader.h
devices/connecteddevice.h devices/connecteddevice.h
@ -499,6 +502,8 @@ set(HEADERS
) )
set(UI set(UI
covers/coversearchstatisticsdialog.ui
devices/deviceproperties.ui devices/deviceproperties.ui
library/groupbydialog.ui library/groupbydialog.ui

View File

@ -23,6 +23,7 @@
const int AlbumCoverFetcher::kMaxConcurrentRequests = 5; const int AlbumCoverFetcher::kMaxConcurrentRequests = 5;
AlbumCoverFetcher::AlbumCoverFetcher(QObject* parent, QNetworkAccessManager* network) AlbumCoverFetcher::AlbumCoverFetcher(QObject* parent, QNetworkAccessManager* network)
: QObject(parent), : QObject(parent),
network_(network ? network : new NetworkAccessManager(this)), network_(network ? network : new NetworkAccessManager(this)),
@ -100,16 +101,17 @@ void AlbumCoverFetcher::StartRequests() {
SLOT(SingleCoverFetched(quint64, const QImage&))); SLOT(SingleCoverFetched(quint64, const QImage&)));
search->Start(); search->Start();
} }
} }
void AlbumCoverFetcher::SingleSearchFinished(quint64 request_id, CoverSearchResults results) { void AlbumCoverFetcher::SingleSearchFinished(quint64 request_id, CoverSearchResults results) {
active_requests_.take(request_id)->deleteLater(); AlbumCoverFetcherSearch* search = active_requests_.take(request_id);
emit SearchFinished(request_id, results); search->deleteLater();
emit SearchFinished(request_id, results, search->statistics());
} }
void AlbumCoverFetcher::SingleCoverFetched(quint64 request_id, const QImage& image) { void AlbumCoverFetcher::SingleCoverFetched(quint64 request_id, const QImage& image) {
active_requests_.take(request_id)->deleteLater(); AlbumCoverFetcherSearch* search = active_requests_.take(request_id);
emit AlbumCoverFetched(request_id, image); search->deleteLater();
emit AlbumCoverFetched(request_id, image, search->statistics());
} }

View File

@ -18,6 +18,8 @@
#ifndef ALBUMCOVERFETCHER_H #ifndef ALBUMCOVERFETCHER_H
#define ALBUMCOVERFETCHER_H #define ALBUMCOVERFETCHER_H
#include "coversearchstatistics.h"
#include <QHash> #include <QHash>
#include <QImage> #include <QImage>
#include <QList> #include <QList>
@ -52,9 +54,9 @@ struct CoverSearchRequest {
// It contains an URL that leads to a found cover plus it's description (usually // It contains an URL that leads to a found cover plus it's description (usually
// the "artist - album" string). // the "artist - album" string).
struct CoverSearchResult { struct CoverSearchResult {
// used for grouping in the user interface. defaults to the name of the // used for grouping in the user interface. This is set automatically - don't
// provider that this result came from. // set it manually in your cover provider.
QString category; QString provider;
// description of this result (we suggest using the "artist - album" format) // description of this result (we suggest using the "artist - album" format)
QString description; QString description;
@ -64,11 +66,13 @@ struct CoverSearchResult {
}; };
Q_DECLARE_METATYPE(CoverSearchResult); Q_DECLARE_METATYPE(CoverSearchResult);
// This is a complete result of a single search request (a list of results, each // This is a complete result of a single search request (a list of results, each
// describing one image, actually). // describing one image, actually).
typedef QList<CoverSearchResult> CoverSearchResults; typedef QList<CoverSearchResult> CoverSearchResults;
Q_DECLARE_METATYPE(QList<CoverSearchResult>); Q_DECLARE_METATYPE(QList<CoverSearchResult>);
// This class searches for album covers for a given query or artist/album and // This class searches for album covers for a given query or artist/album and
// returns URLs. It's NOT thread-safe. // returns URLs. It's NOT thread-safe.
class AlbumCoverFetcher : public QObject { class AlbumCoverFetcher : public QObject {
@ -86,8 +90,10 @@ class AlbumCoverFetcher : public QObject {
void Clear(); void Clear();
signals: signals:
void AlbumCoverFetched(quint64, const QImage& cover); void AlbumCoverFetched(quint64, const QImage& cover,
void SearchFinished(quint64, const CoverSearchResults& results); const CoverSearchStatistics& statistics);
void SearchFinished(quint64, const CoverSearchResults& results,
const CoverSearchStatistics& statistics);
private slots: private slots:
void SingleSearchFinished(quint64, CoverSearchResults results); void SingleSearchFinished(quint64, CoverSearchResults results);

View File

@ -68,6 +68,7 @@ void AlbumCoverFetcherSearch::Start() {
if (success) { if (success) {
pending_requests_[id] = provider; pending_requests_[id] = provider;
statistics_.network_requests_made_ ++;
} }
} }
@ -77,9 +78,9 @@ void AlbumCoverFetcherSearch::Start() {
} }
} }
static bool CompareCategories(const CoverSearchResult& a, static bool CompareProviders(const CoverSearchResult& a,
const CoverSearchResult& b) { const CoverSearchResult& b) {
return a.category < b.category; return a.provider < b.provider;
} }
void AlbumCoverFetcherSearch::ProviderSearchFinished( void AlbumCoverFetcherSearch::ProviderSearchFinished(
@ -90,15 +91,14 @@ void AlbumCoverFetcherSearch::ProviderSearchFinished(
CoverProvider* provider = pending_requests_.take(id); CoverProvider* provider = pending_requests_.take(id);
CoverSearchResults results_copy(results); CoverSearchResults results_copy(results);
// Add categories to the results if the provider didn't specify them // Set categories on the results
for (int i=0 ; i<results_copy.count() ; ++i) { for (int i=0 ; i<results_copy.count() ; ++i) {
if (results_copy[i].category.isEmpty()) { results_copy[i].provider = provider->name();
results_copy[i].category = provider->name();
}
} }
// Add results from the current provider to our pool // Add results from the current provider to our pool
results_.append(results_copy); results_.append(results_copy);
statistics_.total_images_by_provider_[provider->name()] ++;
// do we have more providers left? // do we have more providers left?
if(!pending_requests_.isEmpty()) { if(!pending_requests_.isEmpty()) {
@ -121,6 +121,7 @@ void AlbumCoverFetcherSearch::AllProvidersFinished() {
// no results? // no results?
if (results_.isEmpty()) { if (results_.isEmpty()) {
statistics_.missing_images_ ++;
emit AlbumCoverFetched(request_.id, QImage()); emit AlbumCoverFetched(request_.id, QImage());
return; return;
} }
@ -130,27 +131,29 @@ void AlbumCoverFetcherSearch::AllProvidersFinished() {
// from each category and use some heuristics to score them. If no images // from each category and use some heuristics to score them. If no images
// are good enough we'll keep loading more images until we find one that is // are good enough we'll keep loading more images until we find one that is
// or we run out of results. // or we run out of results.
qStableSort(results_.begin(), results_.end(), CompareCategories); qStableSort(results_.begin(), results_.end(), CompareProviders);
FetchMoreImages(); FetchMoreImages();
} }
void AlbumCoverFetcherSearch::FetchMoreImages() { void AlbumCoverFetcherSearch::FetchMoreImages() {
// Try the first one in each category. // Try the first one in each category.
QString last_category; QString last_provider;
for (int i=0 ; i<results_.count() ; ++i) { for (int i=0 ; i<results_.count() ; ++i) {
if (results_[i].category == last_category) { if (results_[i].provider == last_provider) {
continue; continue;
} }
CoverSearchResult result = results_.takeAt(i--); CoverSearchResult result = results_.takeAt(i--);
last_category = result.category; last_provider = result.provider;
qLog(Debug) << "Loading" << result.image_url << "from" << result.category; qLog(Debug) << "Loading" << result.image_url << "from" << result.provider;
QNetworkReply* image_reply = network_->get(QNetworkRequest(result.image_url)); QNetworkReply* image_reply = network_->get(QNetworkRequest(result.image_url));
connect(image_reply, SIGNAL(finished()), SLOT(ProviderCoverFetchFinished())); connect(image_reply, SIGNAL(finished()), SLOT(ProviderCoverFetchFinished()));
pending_image_loads_ << image_reply; pending_image_loads_[image_reply] = result.provider;
image_load_timeout_->AddReply(image_reply); image_load_timeout_->AddReply(image_reply);
statistics_.network_requests_made_ ++;
} }
if (pending_image_loads_.isEmpty()) { if (pending_image_loads_.isEmpty()) {
@ -162,7 +165,9 @@ void AlbumCoverFetcherSearch::FetchMoreImages() {
void AlbumCoverFetcherSearch::ProviderCoverFetchFinished() { void AlbumCoverFetcherSearch::ProviderCoverFetchFinished() {
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender()); QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
reply->deleteLater(); reply->deleteLater();
pending_image_loads_.removeAll(reply); const QString provider = pending_image_loads_.take(reply);
statistics_.bytes_transferred_ += reply->bytesAvailable();
if (cancel_requested_) { if (cancel_requested_) {
return; return;
@ -176,7 +181,7 @@ void AlbumCoverFetcherSearch::ProviderCoverFetchFinished() {
qLog(Info) << "Error decoding image data from" << reply->url(); qLog(Info) << "Error decoding image data from" << reply->url();
} else { } else {
const float score = ScoreImage(image); const float score = ScoreImage(image);
candidate_images_.insertMulti(score, image); candidate_images_.insertMulti(score, CandidateImage(provider, image));
qLog(Debug) << reply->url() << "scored" << score; qLog(Debug) << reply->url() << "scored" << score;
} }
@ -220,7 +225,15 @@ void AlbumCoverFetcherSearch::SendBestImage() {
QImage image; QImage image;
if (!candidate_images_.isEmpty()) { if (!candidate_images_.isEmpty()) {
image = candidate_images_.values().back(); const CandidateImage best_image = candidate_images_.values().back();
image = best_image.second;
statistics_.chosen_images_by_provider_[best_image.first] ++;
statistics_.chosen_images_ ++;
statistics_.chosen_width_ += image.width();
statistics_.chosen_height_ += image.height();
} else {
statistics_.missing_images_ ++;
} }
emit AlbumCoverFetched(request_.id, image); emit AlbumCoverFetched(request_.id, image);
@ -232,7 +245,7 @@ void AlbumCoverFetcherSearch::Cancel() {
if (!pending_requests_.isEmpty()) { if (!pending_requests_.isEmpty()) {
TerminateSearch(); TerminateSearch();
} else if (!pending_image_loads_.isEmpty()) { } else if (!pending_image_loads_.isEmpty()) {
foreach (QNetworkReply* reply, pending_image_loads_) { foreach (QNetworkReply* reply, pending_image_loads_.keys()) {
reply->abort(); reply->abort();
} }
pending_image_loads_.clear(); pending_image_loads_.clear();

View File

@ -45,9 +45,12 @@ class AlbumCoverFetcherSearch : public QObject {
// is the caller's responsibility to delete the AlbumCoverFetcherSearch. // is the caller's responsibility to delete the AlbumCoverFetcherSearch.
void Cancel(); void Cancel();
CoverSearchStatistics statistics() const { return statistics_; }
signals: signals:
// It's the end of search (when there was no fetch-me-a-cover request). // It's the end of search (when there was no fetch-me-a-cover request).
void SearchFinished(quint64, const CoverSearchResults& results); void SearchFinished(quint64, const CoverSearchResults& results);
// It's the end of search and we've fetched a cover. // It's the end of search and we've fetched a cover.
void AlbumCoverFetched(quint64, const QImage& cover); void AlbumCoverFetched(quint64, const QImage& cover);
@ -69,6 +72,8 @@ private:
static const int kTargetSize; static const int kTargetSize;
static const float kGoodScore; static const float kGoodScore;
CoverSearchStatistics statistics_;
// Search request encapsulated by this AlbumCoverFetcherSearch. // Search request encapsulated by this AlbumCoverFetcherSearch.
CoverSearchRequest request_; CoverSearchRequest request_;
@ -76,11 +81,12 @@ private:
CoverSearchResults results_; CoverSearchResults results_;
QMap<int, CoverProvider*> pending_requests_; QMap<int, CoverProvider*> pending_requests_;
QList<QNetworkReply*> pending_image_loads_; QMap<QNetworkReply*, QString> pending_image_loads_;
NetworkTimeouts* image_load_timeout_; NetworkTimeouts* image_load_timeout_;
// QMap happens to be sorted by key (score) // QMap is sorted by key (score). Values are (provider_name, image)
QMap<float, QImage> candidate_images_; typedef QPair<QString, QImage> CandidateImage;
QMap<float, CandidateImage> candidate_images_;
QNetworkAccessManager* network_; QNetworkAccessManager* network_;

View File

@ -0,0 +1,57 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.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/>.
*/
#include "coversearchstatistics.h"
CoverSearchStatistics::CoverSearchStatistics()
: network_requests_made_(0),
bytes_transferred_(0),
chosen_images_(0),
missing_images_(0),
chosen_width_(0),
chosen_height_(0)
{
}
CoverSearchStatistics& CoverSearchStatistics::operator +=(const CoverSearchStatistics& other) {
network_requests_made_ += other.network_requests_made_;
bytes_transferred_ += other.bytes_transferred_;
foreach (const QString& key, other.chosen_images_by_provider_.keys()) {
chosen_images_by_provider_[key] += other.chosen_images_by_provider_[key];
}
foreach (const QString& key, other.total_images_by_provider_.keys()) {
total_images_by_provider_[key] += other.total_images_by_provider_[key];
}
chosen_images_ += other.chosen_images_;
missing_images_ += other.missing_images_;
chosen_width_ += other.chosen_width_;
chosen_height_ += other.chosen_height_;
return *this;
}
QString CoverSearchStatistics::AverageDimensions() const {
if (chosen_images_ == 0) {
return "0x0";
}
return QString::number(chosen_width_ / chosen_images_) + "x" +
QString::number(chosen_height_ / chosen_images_);
}

View File

@ -0,0 +1,43 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.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 COVERSEARCHSTATISTICS_H
#define COVERSEARCHSTATISTICS_H
#include <QMap>
#include <QString>
struct CoverSearchStatistics {
CoverSearchStatistics();
CoverSearchStatistics& operator +=(const CoverSearchStatistics& other);
quint64 network_requests_made_;
quint64 bytes_transferred_;
QMap<QString, quint64> total_images_by_provider_;
QMap<QString, quint64> chosen_images_by_provider_;
quint64 chosen_images_;
quint64 missing_images_;
quint64 chosen_width_;
quint64 chosen_height_;
QString AverageDimensions() const;
};
#endif // COVERSEARCHSTATISTICS_H

View File

@ -0,0 +1,96 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.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/>.
*/
#include "coversearchstatisticsdialog.h"
#include "ui_coversearchstatisticsdialog.h"
#include "core/utilities.h"
CoverSearchStatisticsDialog::CoverSearchStatisticsDialog(QWidget *parent)
: QDialog(parent),
ui_(new Ui_CoverSearchStatisticsDialog)
{
ui_->setupUi(this);
details_layout_ = new QVBoxLayout(ui_->details);
details_layout_->setSpacing(0);
setStyleSheet(
"#details {"
" background-color: palette(base);"
"}"
"#details QLabel[type=\"label\"] {"
" border: 2px solid transparent;"
" border-right: 2px solid palette(midlight);"
" margin-right: 10px;"
"}"
"#details QLabel[type=\"value\"] {"
" font-weight: bold;"
" max-width: 100px;"
"}"
);
}
CoverSearchStatisticsDialog::~CoverSearchStatisticsDialog() {
delete ui_;
}
void CoverSearchStatisticsDialog::Show(const CoverSearchStatistics& statistics) {
QStringList providers(statistics.total_images_by_provider_.keys());
qSort(providers);
ui_->summary->setText(tr("Got %1 covers out of %2 (%3 failed)")
.arg(statistics.chosen_images_)
.arg(statistics.chosen_images_ + statistics.missing_images_)
.arg(statistics.missing_images_));
foreach (const QString& provider, providers) {
AddLine(tr("Covers from %1").arg(provider),
QString::number(statistics.chosen_images_by_provider_[provider]));
}
if (!providers.isEmpty()) {
AddSpacer();
}
AddLine(tr("Total network requests made"),
QString::number(statistics.network_requests_made_));
AddLine(tr("Average image size"), statistics.AverageDimensions());
AddLine(tr("Total bytes transferred"),
statistics.bytes_transferred_
? Utilities::PrettySize(statistics.bytes_transferred_)
: "0 bytes");
details_layout_->addStretch();
show();
}
void CoverSearchStatisticsDialog::AddLine(const QString& label, const QString& value) {
QLabel* label1 = new QLabel(label);
QLabel* label2 = new QLabel(value);
label1->setProperty("type", "label");
label2->setProperty("type", "value");
QHBoxLayout* layout = new QHBoxLayout;
layout->addWidget(label1);
layout->addWidget(label2);
details_layout_->addLayout(layout);
}
void CoverSearchStatisticsDialog::AddSpacer() {
details_layout_->addSpacing(20);
}

View File

@ -0,0 +1,47 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.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 COVERSEARCHSTATISTICSDIALOG_H
#define COVERSEARCHSTATISTICSDIALOG_H
#include <QDialog>
#include "coversearchstatistics.h"
class Ui_CoverSearchStatisticsDialog;
class QVBoxLayout;
class CoverSearchStatisticsDialog : public QDialog {
Q_OBJECT
public:
CoverSearchStatisticsDialog(QWidget* parent = 0);
~CoverSearchStatisticsDialog();
void Show(const CoverSearchStatistics& statistics);
private:
void AddLine(const QString& label, const QString& value);
void AddSpacer();
private:
Ui_CoverSearchStatisticsDialog* ui_;
QVBoxLayout* details_layout_;
};
#endif // COVERSEARCHSTATISTICSDIALOG_H

View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CoverSearchStatisticsDialog</class>
<widget class="QDialog" name="CoverSearchStatisticsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>425</width>
<height>214</height>
</rect>
</property>
<property name="windowTitle">
<string>Fetch completed</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="summary">
<property name="text">
<string>Got %1 covers out of %2 (%3 failed)</string>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="details">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>CoverSearchStatisticsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>CoverSearchStatisticsDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -54,6 +54,11 @@
#include <taskmanager.h> #include <taskmanager.h>
#include <urlhandler.h> #include <urlhandler.h>
void PythonQtWrapper_AlbumCoverFetcherSearch::Cancel(AlbumCoverFetcherSearch* theWrappedObject)
{
( theWrappedObject->Cancel());
}
void PythonQtWrapper_AlbumCoverFetcherSearch::Start(AlbumCoverFetcherSearch* theWrappedObject) void PythonQtWrapper_AlbumCoverFetcherSearch::Start(AlbumCoverFetcherSearch* theWrappedObject)
{ {
( theWrappedObject->Start()); ( theWrappedObject->Start());

View File

@ -61,6 +61,7 @@ class PythonQtWrapper_AlbumCoverFetcherSearch : public QObject
public: public:
public slots: public slots:
void delete_AlbumCoverFetcherSearch(AlbumCoverFetcherSearch* obj) { delete obj; } void delete_AlbumCoverFetcherSearch(AlbumCoverFetcherSearch* obj) { delete obj; }
void Cancel(AlbumCoverFetcherSearch* theWrappedObject);
void Start(AlbumCoverFetcherSearch* theWrappedObject); void Start(AlbumCoverFetcherSearch* theWrappedObject);
}; };
@ -138,8 +139,8 @@ PythonQtShell_CoverSearchResult* a = new PythonQtShell_CoverSearchResult();
*((CoverSearchResult*)a) = other; *((CoverSearchResult*)a) = other;
return a; } return a; }
void delete_CoverSearchResult(CoverSearchResult* obj) { delete obj; } void delete_CoverSearchResult(CoverSearchResult* obj) { delete obj; }
void py_set_category(CoverSearchResult* theWrappedObject, QString category){ theWrappedObject->category = category; } void py_set_provider(CoverSearchResult* theWrappedObject, QString provider){ theWrappedObject->provider = provider; }
QString py_get_category(CoverSearchResult* theWrappedObject){ return theWrappedObject->category; } QString py_get_provider(CoverSearchResult* theWrappedObject){ return theWrappedObject->provider; }
void py_set_description(CoverSearchResult* theWrappedObject, QString description){ theWrappedObject->description = description; } void py_set_description(CoverSearchResult* theWrappedObject, QString description){ theWrappedObject->description = description; }
QString py_get_description(CoverSearchResult* theWrappedObject){ return theWrappedObject->description; } QString py_get_description(CoverSearchResult* theWrappedObject){ return theWrappedObject->description; }
void py_set_image_url(CoverSearchResult* theWrappedObject, QString image_url){ theWrappedObject->image_url = image_url; } void py_set_image_url(CoverSearchResult* theWrappedObject, QString image_url){ theWrappedObject->image_url = image_url; }

View File

@ -80,7 +80,6 @@ bool PythonScript::Unload() {
// running. This is important because those connections will hold references // running. This is important because those connections will hold references
// to bound methods in the script's classes, so the classes won't get deleted. // to bound methods in the script's classes, so the classes won't get deleted.
foreach (const SignalConnection& conn, signal_connections_) { foreach (const SignalConnection& conn, signal_connections_) {
qLog(Debug) << "Disconnecting signal" << conn.signal_id_;
conn.receiver_->removeSignalHandler(conn.signal_id_, conn.callable_); conn.receiver_->removeSignalHandler(conn.signal_id_, conn.callable_);
} }
@ -120,6 +119,5 @@ bool PythonScript::Unload() {
void PythonScript::RegisterSignalConnection(PythonQtSignalReceiver* receiver, void PythonScript::RegisterSignalConnection(PythonQtSignalReceiver* receiver,
int signal_id, PyObject* callable) { int signal_id, PyObject* callable) {
qLog(Debug) << "Signal" << signal_id << "registered to an object in" << info().id();
signal_connections_ << SignalConnection(receiver, signal_id, callable); signal_connections_ << SignalConnection(receiver, signal_id, callable);
} }

View File

@ -73,6 +73,10 @@ msgstr ""
msgid "%1 tracks" msgid "%1 tracks"
msgstr "" msgstr ""
#, qt-format
msgid "%1 transferred"
msgstr ""
#, qt-format #, qt-format
msgid "%1: Wiimotedev module" msgid "%1: Wiimotedev module"
msgstr "" msgstr ""
@ -472,6 +476,9 @@ msgstr ""
msgid "Average bitrate" msgid "Average bitrate"
msgstr "" msgstr ""
msgid "Average image size"
msgstr ""
msgid "BPM" msgid "BPM"
msgstr "" msgstr ""
@ -1186,6 +1193,9 @@ msgstr ""
msgid "Fetch automatically" msgid "Fetch automatically"
msgstr "" msgstr ""
msgid "Fetch completed"
msgstr ""
msgid "Fetching cover error" msgid "Fetching cover error"
msgstr "" msgstr ""
@ -2834,6 +2844,12 @@ msgstr ""
msgid "Toggle visibility for the pretty on-screen-display" msgid "Toggle visibility for the pretty on-screen-display"
msgstr "" msgstr ""
msgid "Total bytes transferred"
msgstr ""
msgid "Total network requests made"
msgstr ""
msgid "Track" msgid "Track"
msgstr "" msgstr ""

View File

@ -63,6 +63,10 @@ msgstr ""
msgid "%1 tracks" msgid "%1 tracks"
msgstr "" msgstr ""
#, qt-format
msgid "%1 transferred"
msgstr ""
#, qt-format #, qt-format
msgid "%1: Wiimotedev module" msgid "%1: Wiimotedev module"
msgstr "" msgstr ""
@ -462,6 +466,9 @@ msgstr ""
msgid "Average bitrate" msgid "Average bitrate"
msgstr "" msgstr ""
msgid "Average image size"
msgstr ""
msgid "BPM" msgid "BPM"
msgstr "" msgstr ""
@ -1176,6 +1183,9 @@ msgstr ""
msgid "Fetch automatically" msgid "Fetch automatically"
msgstr "" msgstr ""
msgid "Fetch completed"
msgstr ""
msgid "Fetching cover error" msgid "Fetching cover error"
msgstr "" msgstr ""
@ -2824,6 +2834,12 @@ msgstr ""
msgid "Toggle visibility for the pretty on-screen-display" msgid "Toggle visibility for the pretty on-screen-display"
msgstr "" msgstr ""
msgid "Total bytes transferred"
msgstr ""
msgid "Total network requests made"
msgstr ""
msgid "Track" msgid "Track"
msgstr "" msgstr ""

View File

@ -19,8 +19,10 @@
#include "albumcoversearcher.h" #include "albumcoversearcher.h"
#include "iconloader.h" #include "iconloader.h"
#include "ui_albumcovermanager.h" #include "ui_albumcovermanager.h"
#include "core/utilities.h"
#include "covers/albumcoverfetcher.h" #include "covers/albumcoverfetcher.h"
#include "covers/coverproviders.h" #include "covers/coverproviders.h"
#include "covers/coversearchstatisticsdialog.h"
#include "library/librarybackend.h" #include "library/librarybackend.h"
#include "library/libraryquery.h" #include "library/libraryquery.h"
#include "library/sqlrow.h" #include "library/sqlrow.h"
@ -59,9 +61,7 @@ AlbumCoverManager::AlbumCoverManager(LibraryBackend* backend, QWidget* parent,
all_artists_icon_(IconLoader::Load("x-clementine-album")), all_artists_icon_(IconLoader::Load("x-clementine-album")),
context_menu_(new QMenu(this)), context_menu_(new QMenu(this)),
progress_bar_(new QProgressBar(this)), progress_bar_(new QProgressBar(this)),
jobs_(0), jobs_(0)
got_covers_(0),
missing_covers_(0)
{ {
ui_->setupUi(this); ui_->setupUi(this);
ui_->albums->set_cover_manager(this); ui_->albums->set_cover_manager(this);
@ -155,8 +155,8 @@ void AlbumCoverManager::Init() {
connect(filter_group, SIGNAL(triggered(QAction*)), SLOT(UpdateFilter())); connect(filter_group, SIGNAL(triggered(QAction*)), SLOT(UpdateFilter()));
connect(ui_->view, SIGNAL(clicked()), ui_->view, SLOT(showMenu())); connect(ui_->view, SIGNAL(clicked()), ui_->view, SLOT(showMenu()));
connect(ui_->fetch, SIGNAL(clicked()), SLOT(FetchAlbumCovers())); connect(ui_->fetch, SIGNAL(clicked()), SLOT(FetchAlbumCovers()));
connect(cover_fetcher_, SIGNAL(AlbumCoverFetched(quint64,QImage)), connect(cover_fetcher_, SIGNAL(AlbumCoverFetched(quint64,QImage,CoverSearchStatistics)),
SLOT(AlbumCoverFetched(quint64,QImage))); SLOT(AlbumCoverFetched(quint64,QImage,CoverSearchStatistics)));
connect(ui_->action_fetch, SIGNAL(triggered()), SLOT(FetchSingleCover())); connect(ui_->action_fetch, SIGNAL(triggered()), SLOT(FetchSingleCover()));
connect(ui_->albums, SIGNAL(doubleClicked(QModelIndex)), SLOT(AlbumDoubleClicked(QModelIndex))); connect(ui_->albums, SIGNAL(doubleClicked(QModelIndex)), SLOT(AlbumDoubleClicked(QModelIndex)));
connect(ui_->action_add_to_playlist, SIGNAL(triggered()), SLOT(AddSelectedToPlaylist())); connect(ui_->action_add_to_playlist, SIGNAL(triggered()), SLOT(AddSelectedToPlaylist()));
@ -384,19 +384,17 @@ void AlbumCoverManager::FetchAlbumCovers() {
progress_bar_->setMaximum(jobs_); progress_bar_->setMaximum(jobs_);
progress_bar_->show(); progress_bar_->show();
fetch_statistics_ = CoverSearchStatistics();
UpdateStatusText(); UpdateStatusText();
} }
void AlbumCoverManager::AlbumCoverFetched(quint64 id, const QImage &image) { void AlbumCoverManager::AlbumCoverFetched(quint64 id, const QImage& image,
const CoverSearchStatistics& statistics) {
if (!cover_fetching_tasks_.contains(id)) if (!cover_fetching_tasks_.contains(id))
return; return;
QListWidgetItem* item = cover_fetching_tasks_.take(id); QListWidgetItem* item = cover_fetching_tasks_.take(id);
if (image.isNull()) { if (!image.isNull()) {
missing_covers_ ++;
} else {
got_covers_ ++;
SaveAndSetCover(item, image); SaveAndSetCover(item, image);
} }
@ -404,22 +402,34 @@ void AlbumCoverManager::AlbumCoverFetched(quint64 id, const QImage &image) {
ResetFetchCoversButton(); ResetFetchCoversButton();
} }
fetch_statistics_ += statistics;
UpdateStatusText(); UpdateStatusText();
} }
void AlbumCoverManager::UpdateStatusText() { void AlbumCoverManager::UpdateStatusText() {
QString message = tr("Got %1 covers out of %2 (%3 failed)") QString message = tr("Got %1 covers out of %2 (%3 failed)")
.arg(got_covers_).arg(jobs_).arg(missing_covers_); .arg(fetch_statistics_.chosen_images_)
.arg(jobs_)
.arg(fetch_statistics_.missing_images_);
if (fetch_statistics_.bytes_transferred_) {
message += ", " + tr("%1 transferred")
.arg(Utilities::PrettySize(fetch_statistics_.bytes_transferred_));
}
statusBar()->showMessage(message); statusBar()->showMessage(message);
progress_bar_->setValue(got_covers_ + missing_covers_); progress_bar_->setValue(fetch_statistics_.chosen_images_ +
fetch_statistics_.missing_images_);
if (cover_fetching_tasks_.isEmpty()) { if (cover_fetching_tasks_.isEmpty()) {
QTimer::singleShot(2000, statusBar(), SLOT(clearMessage())); QTimer::singleShot(2000, statusBar(), SLOT(clearMessage()));
progress_bar_->hide(); progress_bar_->hide();
CoverSearchStatisticsDialog* dialog = new CoverSearchStatisticsDialog(this);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->Show(fetch_statistics_);
jobs_ = 0; jobs_ = 0;
got_covers_ = 0;
missing_covers_ = 0;
} }
} }

View File

@ -27,6 +27,7 @@
#include "core/backgroundthread.h" #include "core/backgroundthread.h"
#include "core/song.h" #include "core/song.h"
#include "covers/albumcoverloader.h" #include "covers/albumcoverloader.h"
#include "covers/coversearchstatistics.h"
class AlbumCoverChoiceController; class AlbumCoverChoiceController;
class AlbumCoverFetcher; class AlbumCoverFetcher;
@ -78,7 +79,8 @@ class AlbumCoverManager : public QMainWindow {
void CoverImageLoaded(quint64 id, const QImage& image); void CoverImageLoaded(quint64 id, const QImage& image);
void UpdateFilter(); void UpdateFilter();
void FetchAlbumCovers(); void FetchAlbumCovers();
void AlbumCoverFetched(quint64 id, const QImage& image); void AlbumCoverFetched(quint64 id, const QImage& image,
const CoverSearchStatistics& statistics);
// On the context menu // On the context menu
void FetchSingleCover(); void FetchSingleCover();
@ -152,6 +154,7 @@ class AlbumCoverManager : public QMainWindow {
AlbumCoverFetcher* cover_fetcher_; AlbumCoverFetcher* cover_fetcher_;
QMap<quint64, QListWidgetItem*> cover_fetching_tasks_; QMap<quint64, QListWidgetItem*> cover_fetching_tasks_;
CoverSearchStatistics fetch_statistics_;
AlbumCoverSearcher* cover_searcher_; AlbumCoverSearcher* cover_searcher_;
@ -165,8 +168,6 @@ class AlbumCoverManager : public QMainWindow {
QProgressBar* progress_bar_; QProgressBar* progress_bar_;
int jobs_; int jobs_;
int got_covers_;
int missing_covers_;
LineEditInterface* filter_; LineEditInterface* filter_;

View File

@ -64,7 +64,8 @@ AlbumCoverSearcher::~AlbumCoverSearcher() {
void AlbumCoverSearcher::Init(AlbumCoverFetcher* fetcher) { void AlbumCoverSearcher::Init(AlbumCoverFetcher* fetcher) {
fetcher_ = fetcher; fetcher_ = fetcher;
connect(fetcher_, SIGNAL(SearchFinished(quint64,CoverSearchResults)), SLOT(SearchFinished(quint64,CoverSearchResults))); connect(fetcher_, SIGNAL(SearchFinished(quint64,CoverSearchResults,CoverSearchStatistics)),
SLOT(SearchFinished(quint64,CoverSearchResults)));
} }
QImage AlbumCoverSearcher::Exec(const QString& artist, const QString& album) { QImage AlbumCoverSearcher::Exec(const QString& artist, const QString& album) {
@ -126,7 +127,7 @@ void AlbumCoverSearcher::SearchFinished(quint64 id, const CoverSearchResults& re
item->setData(id, Role_ImageRequestId); item->setData(id, Role_ImageRequestId);
item->setData(false, Role_ImageFetchFinished); item->setData(false, Role_ImageFetchFinished);
item->setData(QVariant(Qt::AlignTop | Qt::AlignHCenter), Qt::TextAlignmentRole); item->setData(QVariant(Qt::AlignTop | Qt::AlignHCenter), Qt::TextAlignmentRole);
item->setData(result.category, GroupedIconView::Role_Group); item->setData(result.provider, GroupedIconView::Role_Group);
model_->appendRow(item); model_->appendRow(item);