mirror of
https://github.com/clementine-player/Clementine
synced 2024-12-18 20:34:39 +01:00
Return more useful aggregate information from the PodcastBackend, show something in the Internet service
This commit is contained in:
parent
e8a879372d
commit
f2885c0319
@ -31,6 +31,6 @@ CREATE TABLE podcast_episodes (
|
||||
|
||||
CREATE INDEX podcast_idx_url ON podcasts(url);
|
||||
|
||||
CREATE INDEX podcast_episodes_idx_podcast_id ON podcast_episodes(podcast_id);
|
||||
CREATE INDEX podcast_episodes_idx_aggregate ON podcast_episodes(podcast_id, listened);
|
||||
|
||||
UPDATE schema_version SET version=37;
|
||||
|
@ -38,6 +38,8 @@ Application::Application(QObject* parent)
|
||||
tag_reader_client_(NULL),
|
||||
database_(NULL),
|
||||
album_cover_loader_(NULL),
|
||||
playlist_backend_(NULL),
|
||||
podcast_backend_(NULL),
|
||||
appearance_(NULL),
|
||||
cover_providers_(NULL),
|
||||
task_manager_(NULL),
|
||||
@ -47,9 +49,7 @@ Application::Application(QObject* parent)
|
||||
global_search_(NULL),
|
||||
internet_model_(NULL),
|
||||
library_(NULL),
|
||||
playlist_backend_(NULL),
|
||||
device_manager_(NULL),
|
||||
podcast_backend_(NULL)
|
||||
device_manager_(NULL)
|
||||
{
|
||||
tag_reader_client_ = new TagReaderClient(this);
|
||||
MoveToNewThread(tag_reader_client_);
|
||||
@ -61,6 +61,12 @@ Application::Application(QObject* parent)
|
||||
album_cover_loader_ = new AlbumCoverLoader(this);
|
||||
MoveToNewThread(album_cover_loader_);
|
||||
|
||||
playlist_backend_ = new PlaylistBackend(this, this);
|
||||
MoveToThread(playlist_backend_, database_->thread());
|
||||
|
||||
podcast_backend_ = new PodcastBackend(this, this);
|
||||
MoveToThread(podcast_backend_, database_->thread());
|
||||
|
||||
appearance_ = new Appearance(this);
|
||||
cover_providers_ = new CoverProviders(this);
|
||||
task_manager_ = new TaskManager(this);
|
||||
@ -72,13 +78,8 @@ Application::Application(QObject* parent)
|
||||
|
||||
library_ = new Library(this, this);
|
||||
|
||||
playlist_backend_ = new PlaylistBackend(this, this);
|
||||
MoveToThread(playlist_backend_, database_->thread());
|
||||
|
||||
device_manager_ = new DeviceManager(this, this);
|
||||
|
||||
podcast_backend_ = new PodcastBackend(this, this);
|
||||
MoveToThread(podcast_backend_, database_->thread());
|
||||
|
||||
library_->Init();
|
||||
library_->StartThreads();
|
||||
|
@ -49,6 +49,8 @@ public:
|
||||
TagReaderClient* tag_reader_client() const { return tag_reader_client_; }
|
||||
Database* database() const { return database_; }
|
||||
AlbumCoverLoader* album_cover_loader() const { return album_cover_loader_; }
|
||||
PlaylistBackend* playlist_backend() const { return playlist_backend_; }
|
||||
PodcastBackend* podcast_backend() const { return podcast_backend_; }
|
||||
Appearance* appearance() const { return appearance_; }
|
||||
CoverProviders* cover_providers() const { return cover_providers_; }
|
||||
TaskManager* task_manager() const { return task_manager_; }
|
||||
@ -57,11 +59,8 @@ public:
|
||||
CurrentArtLoader* current_art_loader() const { return current_art_loader_; }
|
||||
GlobalSearch* global_search() const { return global_search_; }
|
||||
InternetModel* internet_model() const { return internet_model_; }
|
||||
|
||||
Library* library() const { return library_; }
|
||||
PlaylistBackend* playlist_backend() const { return playlist_backend_; }
|
||||
DeviceManager* device_manager() const { return device_manager_; }
|
||||
PodcastBackend* podcast_backend() const { return podcast_backend_; }
|
||||
|
||||
LibraryBackend* library_backend() const;
|
||||
LibraryModel* library_model() const;
|
||||
@ -79,6 +78,8 @@ private:
|
||||
TagReaderClient* tag_reader_client_;
|
||||
Database* database_;
|
||||
AlbumCoverLoader* album_cover_loader_;
|
||||
PlaylistBackend* playlist_backend_;
|
||||
PodcastBackend* podcast_backend_;
|
||||
Appearance* appearance_;
|
||||
CoverProviders* cover_providers_;
|
||||
TaskManager* task_manager_;
|
||||
@ -87,11 +88,8 @@ private:
|
||||
CurrentArtLoader* current_art_loader_;
|
||||
GlobalSearch* global_search_;
|
||||
InternetModel* internet_model_;
|
||||
|
||||
Library* library_;
|
||||
PlaylistBackend* playlist_backend_;
|
||||
DeviceManager* device_manager_;
|
||||
PodcastBackend* podcast_backend_;
|
||||
|
||||
QList<QObject*> objects_in_threads_;
|
||||
QList<QThread*> threads_;
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "addpodcastdialog.h"
|
||||
#include "addpodcastbyurl.h"
|
||||
#include "gpoddertoptagspage.h"
|
||||
#include "podcastbackend.h"
|
||||
#include "podcastdiscoverymodel.h"
|
||||
#include "ui_addpodcastdialog.h"
|
||||
#include "core/application.h"
|
||||
@ -28,6 +29,7 @@
|
||||
|
||||
AddPodcastDialog::AddPodcastDialog(Application* app, QWidget* parent)
|
||||
: QDialog(parent),
|
||||
app_(app),
|
||||
ui_(new Ui_AddPodcastDialog)
|
||||
{
|
||||
ui_->setupUi(this);
|
||||
@ -100,7 +102,10 @@ void AddPodcastDialog::ChangePodcast(const QModelIndex& current) {
|
||||
ui_->details_scroll_area->show();
|
||||
}
|
||||
|
||||
ui_->details->SetPodcast(current.data(PodcastDiscoveryModel::Role_Podcast).value<Podcast>());
|
||||
current_podcast_ = current.data(PodcastDiscoveryModel::Role_Podcast).value<Podcast>();
|
||||
ui_->details->SetPodcast(current_podcast_);
|
||||
|
||||
add_button_->setEnabled(current_podcast_.url().isValid());
|
||||
}
|
||||
|
||||
void AddPodcastDialog::PageBusyChanged(bool busy) {
|
||||
@ -116,4 +121,5 @@ void AddPodcastDialog::PageBusyChanged(bool busy) {
|
||||
}
|
||||
|
||||
void AddPodcastDialog::AddPodcast() {
|
||||
app_->podcast_backend()->Subscribe(¤t_podcast_);
|
||||
}
|
||||
|
@ -18,6 +18,8 @@
|
||||
#ifndef ADDPODCASTDIALOG_H
|
||||
#define ADDPODCASTDIALOG_H
|
||||
|
||||
#include "podcast.h"
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class AddPodcastPage;
|
||||
@ -45,6 +47,8 @@ private:
|
||||
void AddPage(AddPodcastPage* page);
|
||||
|
||||
private:
|
||||
Application* app_;
|
||||
|
||||
Ui_AddPodcastDialog* ui_;
|
||||
QPushButton* add_button_;
|
||||
|
||||
@ -52,6 +56,8 @@ private:
|
||||
QList<bool> page_is_busy_;
|
||||
|
||||
WidgetFadeHelper* fader_;
|
||||
|
||||
Podcast current_podcast_;
|
||||
};
|
||||
|
||||
#endif // ADDPODCASTDIALOG_H
|
||||
|
@ -24,9 +24,10 @@
|
||||
|
||||
const QStringList Podcast::kColumns = QStringList()
|
||||
<< "url" << "title" << "description" << "copyright" << "link"
|
||||
<< "image_url" << "author" << "owner_name" << "author_email" << "extra";
|
||||
<< "image_url" << "author" << "owner_name" << "owner_email" << "extra";
|
||||
|
||||
const QString Podcast::kColumnSpec = Podcast::kColumns.join(", ");
|
||||
const QString Podcast::kJoinSpec = Utilities::Prepend("p.", Podcast::kColumns).join(", ");
|
||||
const QString Podcast::kBindSpec = Utilities::Prepend(":", Podcast::kColumns).join(", ");
|
||||
const QString Podcast::kUpdateSpec = Utilities::Updateify(Podcast::kColumns).join(", ");
|
||||
|
||||
|
@ -37,6 +37,7 @@ public:
|
||||
|
||||
static const QStringList kColumns;
|
||||
static const QString kColumnSpec;
|
||||
static const QString kJoinSpec;
|
||||
static const QString kBindSpec;
|
||||
static const QString kUpdateSpec;
|
||||
|
||||
|
@ -48,7 +48,7 @@ void PodcastBackend::Subscribe(Podcast* podcast) {
|
||||
|
||||
// Insert the podcast.
|
||||
QSqlQuery q("INSERT INTO podcasts (" + Podcast::kColumnSpec + ")"
|
||||
" VALUES " + Podcast::kBindSpec, db);
|
||||
" VALUES (" + Podcast::kBindSpec + ")", db);
|
||||
podcast->BindToQuery(&q);
|
||||
|
||||
q.exec();
|
||||
@ -67,11 +67,13 @@ void PodcastBackend::Subscribe(Podcast* podcast) {
|
||||
|
||||
// Add those episodes to the database.
|
||||
AddEpisodes(episodes, &db);
|
||||
|
||||
t.Commit();
|
||||
}
|
||||
|
||||
void PodcastBackend::AddEpisodes(PodcastEpisodeList* episodes, QSqlDatabase* db) {
|
||||
QSqlQuery q("INSERT INTO podcast_episodes (" + PodcastEpisode::kColumnSpec + ")"
|
||||
" VALUES " + PodcastEpisode::kBindSpec, *db);
|
||||
" VALUES (" + PodcastEpisode::kBindSpec + ")", *db);
|
||||
|
||||
for (PodcastEpisodeList::iterator it = episodes->begin() ; it != episodes->end() ; ++it) {
|
||||
it->BindToQuery(&q);
|
||||
@ -84,22 +86,46 @@ void PodcastBackend::AddEpisodes(PodcastEpisodeList* episodes, QSqlDatabase* db)
|
||||
}
|
||||
}
|
||||
|
||||
#define SELECT_PODCAST_QUERY(where_clauses) \
|
||||
"SELECT p.ROWID, " + Podcast::kJoinSpec + "," \
|
||||
" COUNT(e.ROWID), SUM(e.listened)" \
|
||||
" FROM podcasts AS p" \
|
||||
" LEFT JOIN podcast_episodes AS e" \
|
||||
" ON p.ROWID = e.podcast_id" \
|
||||
" " where_clauses \
|
||||
" GROUP BY p.ROWID" \
|
||||
" ORDER BY p.title"
|
||||
|
||||
namespace {
|
||||
void AddAggregatePodcastFields(const QSqlQuery& q, int column_count, Podcast* podcast) {
|
||||
const int episode_count = q.value(column_count + 1).toInt();
|
||||
const int listened_count = q.value(column_count + 2).toInt();
|
||||
|
||||
podcast->set_extra("db:episode_count", episode_count);
|
||||
podcast->set_extra("db:unlistened_count", episode_count - listened_count);
|
||||
}
|
||||
}
|
||||
|
||||
PodcastList PodcastBackend::GetAllSubscriptions() {
|
||||
PodcastList ret;
|
||||
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
QSqlQuery q("SELECT ROWID, " + Podcast::kColumnSpec +
|
||||
" FROM podcasts", db);
|
||||
QSqlQuery q(SELECT_PODCAST_QUERY(""), db);
|
||||
|
||||
q.exec();
|
||||
if (db_->CheckErrors(q))
|
||||
return ret;
|
||||
|
||||
static const int kPodcastColumnCount = Podcast::kColumns.count();
|
||||
|
||||
while (q.next()) {
|
||||
Podcast podcast;
|
||||
podcast.InitFromQuery(q);
|
||||
|
||||
AddAggregatePodcastFields(q, kPodcastColumnCount, &podcast);
|
||||
|
||||
ret << podcast;
|
||||
}
|
||||
|
||||
@ -112,14 +138,12 @@ Podcast PodcastBackend::GetSubscriptionById(int id) {
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
QSqlQuery q("SELECT ROWID, " + Podcast::kColumnSpec +
|
||||
" FROM podcasts"
|
||||
" WHERE ROWID = :id", db);
|
||||
|
||||
QSqlQuery q(SELECT_PODCAST_QUERY("WHERE ROWID = :id"), db);
|
||||
q.bindValue(":id", id);
|
||||
q.exec();
|
||||
if (!db_->CheckErrors(q) && q.next()) {
|
||||
ret.InitFromQuery(q);
|
||||
AddAggregatePodcastFields(q, Podcast::kColumns.count(), &ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
@ -131,14 +155,12 @@ Podcast PodcastBackend::GetSubscriptionByUrl(const QUrl& url) {
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
QSqlQuery q("SELECT ROWID, " + Podcast::kColumnSpec +
|
||||
" FROM podcasts"
|
||||
" WHERE url = :url", db);
|
||||
|
||||
QSqlQuery q(SELECT_PODCAST_QUERY("WHERE p.url = :url"), db);
|
||||
q.bindValue(":url", url.toEncoded());
|
||||
q.exec();
|
||||
if (!db_->CheckErrors(q) && q.next()) {
|
||||
ret.InitFromQuery(q);
|
||||
AddAggregatePodcastFields(q, Podcast::kColumns.count(), &ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
@ -41,6 +41,11 @@ public:
|
||||
// episodes associated with this podcast.
|
||||
void Unsubscribe(const Podcast& podcast);
|
||||
|
||||
// Returns a list of all the subscribed podcasts. For efficiency the Podcast
|
||||
// objects returned won't contain any PodcastEpisode objects, but they will
|
||||
// contain aggregate information about total number of episodes and number of
|
||||
// unlistened episodes, in the extra fields "db:episode_count" and
|
||||
// "db:unlistened_count".
|
||||
PodcastList GetAllSubscriptions();
|
||||
Podcast GetSubscriptionById(int id);
|
||||
Podcast GetSubscriptionByUrl(const QUrl& url);
|
||||
|
@ -27,6 +27,7 @@ const QStringList PodcastEpisode::kColumns = QStringList()
|
||||
<< "downloaded" << "local_url" << "extra";
|
||||
|
||||
const QString PodcastEpisode::kColumnSpec = PodcastEpisode::kColumns.join(", ");
|
||||
const QString PodcastEpisode::kJoinSpec = Utilities::Prepend("e.", PodcastEpisode::kColumns).join(", ");
|
||||
const QString PodcastEpisode::kBindSpec = Utilities::Prepend(":", PodcastEpisode::kColumns).join(", ");
|
||||
const QString PodcastEpisode::kUpdateSpec = Utilities::Updateify(PodcastEpisode::kColumns).join(", ");
|
||||
|
||||
|
@ -31,6 +31,7 @@ public:
|
||||
|
||||
static const QStringList kColumns;
|
||||
static const QString kColumnSpec;
|
||||
static const QString kJoinSpec;
|
||||
static const QString kBindSpec;
|
||||
static const QString kUpdateSpec;
|
||||
|
||||
|
@ -92,6 +92,7 @@ void PodcastInfoWidget::SetPodcast(const Podcast& podcast) {
|
||||
SetText(podcast.author(), ui_->author, ui_->author_label);
|
||||
SetText(podcast.owner_name(), ui_->owner, ui_->owner_label);
|
||||
SetText(podcast.link().toString(), ui_->website, ui_->website_label);
|
||||
SetText(podcast.extra("gpodder:subscribers").toString(), ui_->subscribers, ui_->subscribers_label);
|
||||
|
||||
if (!image_id_) {
|
||||
emit LoadingFinished();
|
||||
|
@ -98,22 +98,12 @@ QLineEdit {
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetMinAndMaxSize</enum>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="copyright_label">
|
||||
<property name="text">
|
||||
<string>Copyright</string>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="website">
|
||||
<property name="frame">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="field_label" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="owner_label">
|
||||
<property name="text">
|
||||
<string>Owner</string>
|
||||
</property>
|
||||
<property name="field_label" stdset="0">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
@ -128,7 +118,17 @@ QLineEdit {
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="owner">
|
||||
<property name="frame">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="website_label">
|
||||
<property name="text">
|
||||
<string>Website</string>
|
||||
@ -148,6 +148,16 @@ QLineEdit {
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="copyright_label">
|
||||
<property name="text">
|
||||
<string>Copyright</string>
|
||||
</property>
|
||||
<property name="field_label" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="author">
|
||||
<property name="frame">
|
||||
@ -158,18 +168,28 @@ QLineEdit {
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="owner">
|
||||
<property name="frame">
|
||||
<bool>false</bool>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="owner_label">
|
||||
<property name="text">
|
||||
<string>Owner</string>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<property name="field_label" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="subscribers_label">
|
||||
<property name="text">
|
||||
<string>Subscribers</string>
|
||||
</property>
|
||||
<property name="field_label" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="website">
|
||||
<widget class="QLineEdit" name="subscribers">
|
||||
<property name="frame">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
|
@ -16,7 +16,9 @@
|
||||
*/
|
||||
|
||||
#include "addpodcastdialog.h"
|
||||
#include "podcastbackend.h"
|
||||
#include "podcastservice.h"
|
||||
#include "core/application.h"
|
||||
#include "internet/internetmodel.h"
|
||||
#include "ui/iconloader.h"
|
||||
|
||||
@ -28,7 +30,8 @@ const char* PodcastService::kSettingsGroup = "Podcasts";
|
||||
PodcastService::PodcastService(Application* app, InternetModel* parent)
|
||||
: InternetService(kServiceName, app, parent, parent),
|
||||
context_menu_(NULL),
|
||||
root_(NULL)
|
||||
root_(NULL),
|
||||
backend_(app->podcast_backend())
|
||||
{
|
||||
}
|
||||
|
||||
@ -37,10 +40,39 @@ PodcastService::~PodcastService() {
|
||||
|
||||
QStandardItem* PodcastService::CreateRootItem() {
|
||||
root_ = new QStandardItem(QIcon(":providers/podcast16.png"), tr("Podcasts"));
|
||||
root_->setData(true, InternetModel::Role_CanLazyLoad);
|
||||
return root_;
|
||||
}
|
||||
|
||||
void PodcastService::LazyPopulate(QStandardItem* parent) {
|
||||
switch (parent->data(InternetModel::Role_Type).toInt()) {
|
||||
case InternetModel::Type_Service:
|
||||
PopulatePodcastList(parent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PodcastService::PopulatePodcastList(QStandardItem* parent) {
|
||||
foreach (const Podcast& podcast, backend_->GetAllSubscriptions()) {
|
||||
const int unlistened_count = podcast.extra("db:unlistened_count").toInt();
|
||||
QString title = podcast.title();
|
||||
|
||||
QStandardItem* item = new QStandardItem;
|
||||
|
||||
if (unlistened_count > 0) {
|
||||
// Add the number of new episodes after the title.
|
||||
title.append(QString(" (%1)").arg(unlistened_count));
|
||||
|
||||
// Set a bold font
|
||||
QFont font(item->font());
|
||||
font.setBold(true);
|
||||
item->setFont(font);
|
||||
}
|
||||
|
||||
item->setText(podcast.title());
|
||||
|
||||
parent->appendRow(item);
|
||||
}
|
||||
}
|
||||
|
||||
void PodcastService::ShowContextMenu(const QModelIndex& index,
|
||||
|
@ -18,12 +18,13 @@
|
||||
#ifndef PODCASTSERVICE_H
|
||||
#define PODCASTSERVICE_H
|
||||
|
||||
#include "internet/internetmodel.h"
|
||||
#include "internet/internetservice.h"
|
||||
|
||||
#include <QScopedPointer>
|
||||
|
||||
class AddPodcastDialog;
|
||||
|
||||
class PodcastBackend;
|
||||
|
||||
class PodcastService : public InternetService {
|
||||
Q_OBJECT
|
||||
@ -35,6 +36,12 @@ public:
|
||||
static const char* kServiceName;
|
||||
static const char* kSettingsGroup;
|
||||
|
||||
enum Type {
|
||||
Type_AddPodcast = InternetModel::TypeCount,
|
||||
Type_Podcast,
|
||||
Type_Episode
|
||||
};
|
||||
|
||||
QStandardItem* CreateRootItem();
|
||||
void LazyPopulate(QStandardItem* parent);
|
||||
|
||||
@ -46,10 +53,15 @@ protected:
|
||||
private slots:
|
||||
void AddPodcast();
|
||||
|
||||
private:
|
||||
void PopulatePodcastList(QStandardItem* parent);
|
||||
|
||||
private:
|
||||
QMenu* context_menu_;
|
||||
QStandardItem* root_;
|
||||
|
||||
PodcastBackend* backend_;
|
||||
|
||||
QScopedPointer<AddPodcastDialog> add_podcast_dialog_;
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user