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:
parent
42801a967b
commit
7773e98ebf
|
@ -100,6 +100,8 @@ set(SOURCES
|
|||
covers/artloader.cpp
|
||||
covers/coverprovider.cpp
|
||||
covers/coverproviders.cpp
|
||||
covers/coversearchstatistics.cpp
|
||||
covers/coversearchstatisticsdialog.cpp
|
||||
covers/kittenloader.cpp
|
||||
|
||||
devices/connecteddevice.cpp
|
||||
|
@ -329,6 +331,7 @@ set(HEADERS
|
|||
covers/artloader.h
|
||||
covers/coverprovider.h
|
||||
covers/coverproviders.h
|
||||
covers/coversearchstatisticsdialog.h
|
||||
covers/kittenloader.h
|
||||
|
||||
devices/connecteddevice.h
|
||||
|
@ -499,6 +502,8 @@ set(HEADERS
|
|||
)
|
||||
|
||||
set(UI
|
||||
covers/coversearchstatisticsdialog.ui
|
||||
|
||||
devices/deviceproperties.ui
|
||||
|
||||
library/groupbydialog.ui
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
const int AlbumCoverFetcher::kMaxConcurrentRequests = 5;
|
||||
|
||||
|
||||
AlbumCoverFetcher::AlbumCoverFetcher(QObject* parent, QNetworkAccessManager* network)
|
||||
: QObject(parent),
|
||||
network_(network ? network : new NetworkAccessManager(this)),
|
||||
|
@ -100,16 +101,17 @@ void AlbumCoverFetcher::StartRequests() {
|
|||
SLOT(SingleCoverFetched(quint64, const QImage&)));
|
||||
|
||||
search->Start();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void AlbumCoverFetcher::SingleSearchFinished(quint64 request_id, CoverSearchResults results) {
|
||||
active_requests_.take(request_id)->deleteLater();
|
||||
emit SearchFinished(request_id, results);
|
||||
AlbumCoverFetcherSearch* search = active_requests_.take(request_id);
|
||||
search->deleteLater();
|
||||
emit SearchFinished(request_id, results, search->statistics());
|
||||
}
|
||||
|
||||
void AlbumCoverFetcher::SingleCoverFetched(quint64 request_id, const QImage& image) {
|
||||
active_requests_.take(request_id)->deleteLater();
|
||||
emit AlbumCoverFetched(request_id, image);
|
||||
AlbumCoverFetcherSearch* search = active_requests_.take(request_id);
|
||||
search->deleteLater();
|
||||
emit AlbumCoverFetched(request_id, image, search->statistics());
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
#ifndef ALBUMCOVERFETCHER_H
|
||||
#define ALBUMCOVERFETCHER_H
|
||||
|
||||
#include "coversearchstatistics.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QImage>
|
||||
#include <QList>
|
||||
|
@ -52,9 +54,9 @@ struct CoverSearchRequest {
|
|||
// It contains an URL that leads to a found cover plus it's description (usually
|
||||
// the "artist - album" string).
|
||||
struct CoverSearchResult {
|
||||
// used for grouping in the user interface. defaults to the name of the
|
||||
// provider that this result came from.
|
||||
QString category;
|
||||
// used for grouping in the user interface. This is set automatically - don't
|
||||
// set it manually in your cover provider.
|
||||
QString provider;
|
||||
|
||||
// description of this result (we suggest using the "artist - album" format)
|
||||
QString description;
|
||||
|
@ -64,11 +66,13 @@ struct CoverSearchResult {
|
|||
};
|
||||
Q_DECLARE_METATYPE(CoverSearchResult);
|
||||
|
||||
|
||||
// This is a complete result of a single search request (a list of results, each
|
||||
// describing one image, actually).
|
||||
typedef QList<CoverSearchResult> CoverSearchResults;
|
||||
Q_DECLARE_METATYPE(QList<CoverSearchResult>);
|
||||
|
||||
|
||||
// This class searches for album covers for a given query or artist/album and
|
||||
// returns URLs. It's NOT thread-safe.
|
||||
class AlbumCoverFetcher : public QObject {
|
||||
|
@ -86,8 +90,10 @@ class AlbumCoverFetcher : public QObject {
|
|||
void Clear();
|
||||
|
||||
signals:
|
||||
void AlbumCoverFetched(quint64, const QImage& cover);
|
||||
void SearchFinished(quint64, const CoverSearchResults& results);
|
||||
void AlbumCoverFetched(quint64, const QImage& cover,
|
||||
const CoverSearchStatistics& statistics);
|
||||
void SearchFinished(quint64, const CoverSearchResults& results,
|
||||
const CoverSearchStatistics& statistics);
|
||||
|
||||
private slots:
|
||||
void SingleSearchFinished(quint64, CoverSearchResults results);
|
||||
|
|
|
@ -68,6 +68,7 @@ void AlbumCoverFetcherSearch::Start() {
|
|||
|
||||
if (success) {
|
||||
pending_requests_[id] = provider;
|
||||
statistics_.network_requests_made_ ++;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,9 +78,9 @@ void AlbumCoverFetcherSearch::Start() {
|
|||
}
|
||||
}
|
||||
|
||||
static bool CompareCategories(const CoverSearchResult& a,
|
||||
const CoverSearchResult& b) {
|
||||
return a.category < b.category;
|
||||
static bool CompareProviders(const CoverSearchResult& a,
|
||||
const CoverSearchResult& b) {
|
||||
return a.provider < b.provider;
|
||||
}
|
||||
|
||||
void AlbumCoverFetcherSearch::ProviderSearchFinished(
|
||||
|
@ -90,15 +91,14 @@ void AlbumCoverFetcherSearch::ProviderSearchFinished(
|
|||
CoverProvider* provider = pending_requests_.take(id);
|
||||
|
||||
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) {
|
||||
if (results_copy[i].category.isEmpty()) {
|
||||
results_copy[i].category = provider->name();
|
||||
}
|
||||
results_copy[i].provider = provider->name();
|
||||
}
|
||||
|
||||
// Add results from the current provider to our pool
|
||||
results_.append(results_copy);
|
||||
statistics_.total_images_by_provider_[provider->name()] ++;
|
||||
|
||||
// do we have more providers left?
|
||||
if(!pending_requests_.isEmpty()) {
|
||||
|
@ -121,6 +121,7 @@ void AlbumCoverFetcherSearch::AllProvidersFinished() {
|
|||
|
||||
// no results?
|
||||
if (results_.isEmpty()) {
|
||||
statistics_.missing_images_ ++;
|
||||
emit AlbumCoverFetched(request_.id, QImage());
|
||||
return;
|
||||
}
|
||||
|
@ -130,27 +131,29 @@ void AlbumCoverFetcherSearch::AllProvidersFinished() {
|
|||
// 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
|
||||
// or we run out of results.
|
||||
qStableSort(results_.begin(), results_.end(), CompareCategories);
|
||||
qStableSort(results_.begin(), results_.end(), CompareProviders);
|
||||
FetchMoreImages();
|
||||
}
|
||||
|
||||
void AlbumCoverFetcherSearch::FetchMoreImages() {
|
||||
// Try the first one in each category.
|
||||
QString last_category;
|
||||
QString last_provider;
|
||||
for (int i=0 ; i<results_.count() ; ++i) {
|
||||
if (results_[i].category == last_category) {
|
||||
if (results_[i].provider == last_provider) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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));
|
||||
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);
|
||||
|
||||
statistics_.network_requests_made_ ++;
|
||||
}
|
||||
|
||||
if (pending_image_loads_.isEmpty()) {
|
||||
|
@ -162,7 +165,9 @@ void AlbumCoverFetcherSearch::FetchMoreImages() {
|
|||
void AlbumCoverFetcherSearch::ProviderCoverFetchFinished() {
|
||||
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
|
||||
reply->deleteLater();
|
||||
pending_image_loads_.removeAll(reply);
|
||||
const QString provider = pending_image_loads_.take(reply);
|
||||
|
||||
statistics_.bytes_transferred_ += reply->bytesAvailable();
|
||||
|
||||
if (cancel_requested_) {
|
||||
return;
|
||||
|
@ -176,7 +181,7 @@ void AlbumCoverFetcherSearch::ProviderCoverFetchFinished() {
|
|||
qLog(Info) << "Error decoding image data from" << reply->url();
|
||||
} else {
|
||||
const float score = ScoreImage(image);
|
||||
candidate_images_.insertMulti(score, image);
|
||||
candidate_images_.insertMulti(score, CandidateImage(provider, image));
|
||||
|
||||
qLog(Debug) << reply->url() << "scored" << score;
|
||||
}
|
||||
|
@ -220,7 +225,15 @@ void AlbumCoverFetcherSearch::SendBestImage() {
|
|||
QImage image;
|
||||
|
||||
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);
|
||||
|
@ -232,7 +245,7 @@ void AlbumCoverFetcherSearch::Cancel() {
|
|||
if (!pending_requests_.isEmpty()) {
|
||||
TerminateSearch();
|
||||
} else if (!pending_image_loads_.isEmpty()) {
|
||||
foreach (QNetworkReply* reply, pending_image_loads_) {
|
||||
foreach (QNetworkReply* reply, pending_image_loads_.keys()) {
|
||||
reply->abort();
|
||||
}
|
||||
pending_image_loads_.clear();
|
||||
|
|
|
@ -45,9 +45,12 @@ class AlbumCoverFetcherSearch : public QObject {
|
|||
// is the caller's responsibility to delete the AlbumCoverFetcherSearch.
|
||||
void Cancel();
|
||||
|
||||
CoverSearchStatistics statistics() const { return statistics_; }
|
||||
|
||||
signals:
|
||||
// It's the end of search (when there was no fetch-me-a-cover request).
|
||||
void SearchFinished(quint64, const CoverSearchResults& results);
|
||||
|
||||
// It's the end of search and we've fetched a cover.
|
||||
void AlbumCoverFetched(quint64, const QImage& cover);
|
||||
|
||||
|
@ -69,6 +72,8 @@ private:
|
|||
static const int kTargetSize;
|
||||
static const float kGoodScore;
|
||||
|
||||
CoverSearchStatistics statistics_;
|
||||
|
||||
// Search request encapsulated by this AlbumCoverFetcherSearch.
|
||||
CoverSearchRequest request_;
|
||||
|
||||
|
@ -76,11 +81,12 @@ private:
|
|||
CoverSearchResults results_;
|
||||
|
||||
QMap<int, CoverProvider*> pending_requests_;
|
||||
QList<QNetworkReply*> pending_image_loads_;
|
||||
QMap<QNetworkReply*, QString> pending_image_loads_;
|
||||
NetworkTimeouts* image_load_timeout_;
|
||||
|
||||
// QMap happens to be sorted by key (score)
|
||||
QMap<float, QImage> candidate_images_;
|
||||
// QMap is sorted by key (score). Values are (provider_name, image)
|
||||
typedef QPair<QString, QImage> CandidateImage;
|
||||
QMap<float, CandidateImage> candidate_images_;
|
||||
|
||||
QNetworkAccessManager* network_;
|
||||
|
||||
|
|
|
@ -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_);
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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>
|
|
@ -54,6 +54,11 @@
|
|||
#include <taskmanager.h>
|
||||
#include <urlhandler.h>
|
||||
|
||||
void PythonQtWrapper_AlbumCoverFetcherSearch::Cancel(AlbumCoverFetcherSearch* theWrappedObject)
|
||||
{
|
||||
( theWrappedObject->Cancel());
|
||||
}
|
||||
|
||||
void PythonQtWrapper_AlbumCoverFetcherSearch::Start(AlbumCoverFetcherSearch* theWrappedObject)
|
||||
{
|
||||
( theWrappedObject->Start());
|
||||
|
|
|
@ -61,6 +61,7 @@ class PythonQtWrapper_AlbumCoverFetcherSearch : public QObject
|
|||
public:
|
||||
public slots:
|
||||
void delete_AlbumCoverFetcherSearch(AlbumCoverFetcherSearch* obj) { delete obj; }
|
||||
void Cancel(AlbumCoverFetcherSearch* theWrappedObject);
|
||||
void Start(AlbumCoverFetcherSearch* theWrappedObject);
|
||||
};
|
||||
|
||||
|
@ -138,8 +139,8 @@ PythonQtShell_CoverSearchResult* a = new PythonQtShell_CoverSearchResult();
|
|||
*((CoverSearchResult*)a) = other;
|
||||
return a; }
|
||||
void delete_CoverSearchResult(CoverSearchResult* obj) { delete obj; }
|
||||
void py_set_category(CoverSearchResult* theWrappedObject, QString category){ theWrappedObject->category = category; }
|
||||
QString py_get_category(CoverSearchResult* theWrappedObject){ return theWrappedObject->category; }
|
||||
void py_set_provider(CoverSearchResult* theWrappedObject, QString provider){ theWrappedObject->provider = provider; }
|
||||
QString py_get_provider(CoverSearchResult* theWrappedObject){ return theWrappedObject->provider; }
|
||||
void py_set_description(CoverSearchResult* theWrappedObject, QString description){ theWrappedObject->description = 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; }
|
||||
|
|
|
@ -80,7 +80,6 @@ bool PythonScript::Unload() {
|
|||
// 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.
|
||||
foreach (const SignalConnection& conn, signal_connections_) {
|
||||
qLog(Debug) << "Disconnecting signal" << conn.signal_id_;
|
||||
conn.receiver_->removeSignalHandler(conn.signal_id_, conn.callable_);
|
||||
}
|
||||
|
||||
|
@ -120,6 +119,5 @@ bool PythonScript::Unload() {
|
|||
|
||||
void PythonScript::RegisterSignalConnection(PythonQtSignalReceiver* receiver,
|
||||
int signal_id, PyObject* callable) {
|
||||
qLog(Debug) << "Signal" << signal_id << "registered to an object in" << info().id();
|
||||
signal_connections_ << SignalConnection(receiver, signal_id, callable);
|
||||
}
|
||||
|
|
|
@ -73,6 +73,10 @@ msgstr ""
|
|||
msgid "%1 tracks"
|
||||
msgstr ""
|
||||
|
||||
#, qt-format
|
||||
msgid "%1 transferred"
|
||||
msgstr ""
|
||||
|
||||
#, qt-format
|
||||
msgid "%1: Wiimotedev module"
|
||||
msgstr ""
|
||||
|
@ -472,6 +476,9 @@ msgstr ""
|
|||
msgid "Average bitrate"
|
||||
msgstr ""
|
||||
|
||||
msgid "Average image size"
|
||||
msgstr ""
|
||||
|
||||
msgid "BPM"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1186,6 +1193,9 @@ msgstr ""
|
|||
msgid "Fetch automatically"
|
||||
msgstr ""
|
||||
|
||||
msgid "Fetch completed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Fetching cover error"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2834,6 +2844,12 @@ msgstr ""
|
|||
msgid "Toggle visibility for the pretty on-screen-display"
|
||||
msgstr ""
|
||||
|
||||
msgid "Total bytes transferred"
|
||||
msgstr ""
|
||||
|
||||
msgid "Total network requests made"
|
||||
msgstr ""
|
||||
|
||||
msgid "Track"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -63,6 +63,10 @@ msgstr ""
|
|||
msgid "%1 tracks"
|
||||
msgstr ""
|
||||
|
||||
#, qt-format
|
||||
msgid "%1 transferred"
|
||||
msgstr ""
|
||||
|
||||
#, qt-format
|
||||
msgid "%1: Wiimotedev module"
|
||||
msgstr ""
|
||||
|
@ -462,6 +466,9 @@ msgstr ""
|
|||
msgid "Average bitrate"
|
||||
msgstr ""
|
||||
|
||||
msgid "Average image size"
|
||||
msgstr ""
|
||||
|
||||
msgid "BPM"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1176,6 +1183,9 @@ msgstr ""
|
|||
msgid "Fetch automatically"
|
||||
msgstr ""
|
||||
|
||||
msgid "Fetch completed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Fetching cover error"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2824,6 +2834,12 @@ msgstr ""
|
|||
msgid "Toggle visibility for the pretty on-screen-display"
|
||||
msgstr ""
|
||||
|
||||
msgid "Total bytes transferred"
|
||||
msgstr ""
|
||||
|
||||
msgid "Total network requests made"
|
||||
msgstr ""
|
||||
|
||||
msgid "Track"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -19,8 +19,10 @@
|
|||
#include "albumcoversearcher.h"
|
||||
#include "iconloader.h"
|
||||
#include "ui_albumcovermanager.h"
|
||||
#include "core/utilities.h"
|
||||
#include "covers/albumcoverfetcher.h"
|
||||
#include "covers/coverproviders.h"
|
||||
#include "covers/coversearchstatisticsdialog.h"
|
||||
#include "library/librarybackend.h"
|
||||
#include "library/libraryquery.h"
|
||||
#include "library/sqlrow.h"
|
||||
|
@ -59,9 +61,7 @@ AlbumCoverManager::AlbumCoverManager(LibraryBackend* backend, QWidget* parent,
|
|||
all_artists_icon_(IconLoader::Load("x-clementine-album")),
|
||||
context_menu_(new QMenu(this)),
|
||||
progress_bar_(new QProgressBar(this)),
|
||||
jobs_(0),
|
||||
got_covers_(0),
|
||||
missing_covers_(0)
|
||||
jobs_(0)
|
||||
{
|
||||
ui_->setupUi(this);
|
||||
ui_->albums->set_cover_manager(this);
|
||||
|
@ -155,8 +155,8 @@ void AlbumCoverManager::Init() {
|
|||
connect(filter_group, SIGNAL(triggered(QAction*)), SLOT(UpdateFilter()));
|
||||
connect(ui_->view, SIGNAL(clicked()), ui_->view, SLOT(showMenu()));
|
||||
connect(ui_->fetch, SIGNAL(clicked()), SLOT(FetchAlbumCovers()));
|
||||
connect(cover_fetcher_, SIGNAL(AlbumCoverFetched(quint64,QImage)),
|
||||
SLOT(AlbumCoverFetched(quint64,QImage)));
|
||||
connect(cover_fetcher_, SIGNAL(AlbumCoverFetched(quint64,QImage,CoverSearchStatistics)),
|
||||
SLOT(AlbumCoverFetched(quint64,QImage,CoverSearchStatistics)));
|
||||
connect(ui_->action_fetch, SIGNAL(triggered()), SLOT(FetchSingleCover()));
|
||||
connect(ui_->albums, SIGNAL(doubleClicked(QModelIndex)), SLOT(AlbumDoubleClicked(QModelIndex)));
|
||||
connect(ui_->action_add_to_playlist, SIGNAL(triggered()), SLOT(AddSelectedToPlaylist()));
|
||||
|
@ -384,19 +384,17 @@ void AlbumCoverManager::FetchAlbumCovers() {
|
|||
|
||||
progress_bar_->setMaximum(jobs_);
|
||||
progress_bar_->show();
|
||||
fetch_statistics_ = CoverSearchStatistics();
|
||||
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))
|
||||
return;
|
||||
|
||||
QListWidgetItem* item = cover_fetching_tasks_.take(id);
|
||||
if (image.isNull()) {
|
||||
missing_covers_ ++;
|
||||
} else {
|
||||
got_covers_ ++;
|
||||
|
||||
if (!image.isNull()) {
|
||||
SaveAndSetCover(item, image);
|
||||
}
|
||||
|
||||
|
@ -404,22 +402,34 @@ void AlbumCoverManager::AlbumCoverFetched(quint64 id, const QImage &image) {
|
|||
ResetFetchCoversButton();
|
||||
}
|
||||
|
||||
fetch_statistics_ += statistics;
|
||||
UpdateStatusText();
|
||||
}
|
||||
|
||||
void AlbumCoverManager::UpdateStatusText() {
|
||||
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);
|
||||
progress_bar_->setValue(got_covers_ + missing_covers_);
|
||||
progress_bar_->setValue(fetch_statistics_.chosen_images_ +
|
||||
fetch_statistics_.missing_images_);
|
||||
|
||||
if (cover_fetching_tasks_.isEmpty()) {
|
||||
QTimer::singleShot(2000, statusBar(), SLOT(clearMessage()));
|
||||
progress_bar_->hide();
|
||||
|
||||
CoverSearchStatisticsDialog* dialog = new CoverSearchStatisticsDialog(this);
|
||||
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
dialog->Show(fetch_statistics_);
|
||||
|
||||
jobs_ = 0;
|
||||
got_covers_ = 0;
|
||||
missing_covers_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "core/backgroundthread.h"
|
||||
#include "core/song.h"
|
||||
#include "covers/albumcoverloader.h"
|
||||
#include "covers/coversearchstatistics.h"
|
||||
|
||||
class AlbumCoverChoiceController;
|
||||
class AlbumCoverFetcher;
|
||||
|
@ -78,7 +79,8 @@ class AlbumCoverManager : public QMainWindow {
|
|||
void CoverImageLoaded(quint64 id, const QImage& image);
|
||||
void UpdateFilter();
|
||||
void FetchAlbumCovers();
|
||||
void AlbumCoverFetched(quint64 id, const QImage& image);
|
||||
void AlbumCoverFetched(quint64 id, const QImage& image,
|
||||
const CoverSearchStatistics& statistics);
|
||||
|
||||
// On the context menu
|
||||
void FetchSingleCover();
|
||||
|
@ -152,6 +154,7 @@ class AlbumCoverManager : public QMainWindow {
|
|||
|
||||
AlbumCoverFetcher* cover_fetcher_;
|
||||
QMap<quint64, QListWidgetItem*> cover_fetching_tasks_;
|
||||
CoverSearchStatistics fetch_statistics_;
|
||||
|
||||
AlbumCoverSearcher* cover_searcher_;
|
||||
|
||||
|
@ -165,8 +168,6 @@ class AlbumCoverManager : public QMainWindow {
|
|||
|
||||
QProgressBar* progress_bar_;
|
||||
int jobs_;
|
||||
int got_covers_;
|
||||
int missing_covers_;
|
||||
|
||||
LineEditInterface* filter_;
|
||||
|
||||
|
|
|
@ -64,7 +64,8 @@ AlbumCoverSearcher::~AlbumCoverSearcher() {
|
|||
void AlbumCoverSearcher::Init(AlbumCoverFetcher* 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) {
|
||||
|
@ -126,7 +127,7 @@ void AlbumCoverSearcher::SearchFinished(quint64 id, const CoverSearchResults& re
|
|||
item->setData(id, Role_ImageRequestId);
|
||||
item->setData(false, Role_ImageFetchFinished);
|
||||
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);
|
||||
|
||||
|
|
Loading…
Reference in New Issue