Return more useful aggregate information from the PodcastBackend, show something in the Internet service

This commit is contained in:
David Sansome 2012-03-06 18:37:46 +00:00
parent e8a879372d
commit f2885c0319
15 changed files with 160 additions and 53 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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(", ");

View File

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

View File

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

View File

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

View File

@ -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(", ");

View File

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

View File

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

View File

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

View File

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

View File

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