From f2885c0319076115dfb57d9fa8164603210662f6 Mon Sep 17 00:00:00 2001 From: David Sansome Date: Tue, 6 Mar 2012 18:37:46 +0000 Subject: [PATCH] Return more useful aggregate information from the PodcastBackend, show something in the Internet service --- data/schema/schema-37.sql | 2 +- src/core/application.cpp | 17 ++++---- src/core/application.h | 10 ++--- src/podcasts/addpodcastdialog.cpp | 8 +++- src/podcasts/addpodcastdialog.h | 6 +++ src/podcasts/podcast.cpp | 3 +- src/podcasts/podcast.h | 1 + src/podcasts/podcastbackend.cpp | 46 +++++++++++++++------ src/podcasts/podcastbackend.h | 5 +++ src/podcasts/podcastepisode.cpp | 1 + src/podcasts/podcastepisode.h | 1 + src/podcasts/podcastinfowidget.cpp | 1 + src/podcasts/podcastinfowidget.ui | 64 ++++++++++++++++++++---------- src/podcasts/podcastservice.cpp | 34 +++++++++++++++- src/podcasts/podcastservice.h | 14 ++++++- 15 files changed, 160 insertions(+), 53 deletions(-) diff --git a/data/schema/schema-37.sql b/data/schema/schema-37.sql index e7366ab3c..c83995408 100644 --- a/data/schema/schema-37.sql +++ b/data/schema/schema-37.sql @@ -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; diff --git a/src/core/application.cpp b/src/core/application.cpp index af623aa72..8929a926c 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -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(); diff --git a/src/core/application.h b/src/core/application.h index ae14058fc..a775c2530 100644 --- a/src/core/application.h +++ b/src/core/application.h @@ -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 objects_in_threads_; QList threads_; diff --git a/src/podcasts/addpodcastdialog.cpp b/src/podcasts/addpodcastdialog.cpp index 4b6606fa5..61fa61c6d 100644 --- a/src/podcasts/addpodcastdialog.cpp +++ b/src/podcasts/addpodcastdialog.cpp @@ -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()); + current_podcast_ = current.data(PodcastDiscoveryModel::Role_Podcast).value(); + 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_); } diff --git a/src/podcasts/addpodcastdialog.h b/src/podcasts/addpodcastdialog.h index 1fc881031..c216a21d3 100644 --- a/src/podcasts/addpodcastdialog.h +++ b/src/podcasts/addpodcastdialog.h @@ -18,6 +18,8 @@ #ifndef ADDPODCASTDIALOG_H #define ADDPODCASTDIALOG_H +#include "podcast.h" + #include 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 page_is_busy_; WidgetFadeHelper* fader_; + + Podcast current_podcast_; }; #endif // ADDPODCASTDIALOG_H diff --git a/src/podcasts/podcast.cpp b/src/podcasts/podcast.cpp index bd4b97692..f5971b86d 100644 --- a/src/podcasts/podcast.cpp +++ b/src/podcasts/podcast.cpp @@ -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(", "); diff --git a/src/podcasts/podcast.h b/src/podcasts/podcast.h index 047b50a8b..469772106 100644 --- a/src/podcasts/podcast.h +++ b/src/podcasts/podcast.h @@ -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; diff --git a/src/podcasts/podcastbackend.cpp b/src/podcasts/podcastbackend.cpp index 0de1b6ed2..c9cb654b3 100644 --- a/src/podcasts/podcastbackend.cpp +++ b/src/podcasts/podcastbackend.cpp @@ -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; diff --git a/src/podcasts/podcastbackend.h b/src/podcasts/podcastbackend.h index ca959e751..d8d64a2af 100644 --- a/src/podcasts/podcastbackend.h +++ b/src/podcasts/podcastbackend.h @@ -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); diff --git a/src/podcasts/podcastepisode.cpp b/src/podcasts/podcastepisode.cpp index 762b8eaf2..3e88297ec 100644 --- a/src/podcasts/podcastepisode.cpp +++ b/src/podcasts/podcastepisode.cpp @@ -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(", "); diff --git a/src/podcasts/podcastepisode.h b/src/podcasts/podcastepisode.h index cf29ad2a2..e748594ad 100644 --- a/src/podcasts/podcastepisode.h +++ b/src/podcasts/podcastepisode.h @@ -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; diff --git a/src/podcasts/podcastinfowidget.cpp b/src/podcasts/podcastinfowidget.cpp index 345dd0700..0a5287d01 100644 --- a/src/podcasts/podcastinfowidget.cpp +++ b/src/podcasts/podcastinfowidget.cpp @@ -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(); diff --git a/src/podcasts/podcastinfowidget.ui b/src/podcasts/podcastinfowidget.ui index bc31b46a7..0f437ee40 100644 --- a/src/podcasts/podcastinfowidget.ui +++ b/src/podcasts/podcastinfowidget.ui @@ -98,22 +98,12 @@ QLineEdit { QLayout::SetMinAndMaxSize - - - - Copyright + + + + false - - true - - - - - - - Owner - - + true @@ -128,7 +118,17 @@ QLineEdit { - + + + + false + + + true + + + + Website @@ -148,6 +148,16 @@ QLineEdit { + + + + Copyright + + + true + + + @@ -158,18 +168,28 @@ QLineEdit { - - - - false + + + + Owner - + + true + + + + + + + Subscribers + + true - + false diff --git a/src/podcasts/podcastservice.cpp b/src/podcasts/podcastservice.cpp index 77fcb180c..b525cccea 100644 --- a/src/podcasts/podcastservice.cpp +++ b/src/podcasts/podcastservice.cpp @@ -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, diff --git a/src/podcasts/podcastservice.h b/src/podcasts/podcastservice.h index 73c5e634e..1a2e6447d 100644 --- a/src/podcasts/podcastservice.h +++ b/src/podcasts/podcastservice.h @@ -18,12 +18,13 @@ #ifndef PODCASTSERVICE_H #define PODCASTSERVICE_H +#include "internet/internetmodel.h" #include "internet/internetservice.h" #include 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 add_podcast_dialog_; };