Refactor EpisodeModel and DownloadModel to make them more performant

This commit is contained in:
Bart De Vries 2021-09-08 11:47:32 +02:00
parent 117f314d0d
commit bb8cd2807c
6 changed files with 66 additions and 138 deletions

View File

@ -147,51 +147,6 @@ Entry *DataManager::getEntry(const QString &id) const
return m_entries[id];
}
Entry *DataManager::getEntry(const EpisodeModel::Type type, const int entry_index) const
{
QSqlQuery entryQuery;
if (type == EpisodeModel::All || type == EpisodeModel::New || type == EpisodeModel::Unread || type == EpisodeModel::Downloading
|| type == EpisodeModel::PartiallyDownloaded || type == EpisodeModel::Downloaded) {
if (type == EpisodeModel::New) {
entryQuery.prepare(QStringLiteral("SELECT id FROM Entries WHERE new=:new ORDER BY updated DESC LIMIT 1 OFFSET :index;"));
entryQuery.bindValue(QStringLiteral(":new"), true);
} else if (type == EpisodeModel::Unread) {
entryQuery.prepare(QStringLiteral("SELECT id FROM Entries WHERE read=:read ORDER BY updated DESC LIMIT 1 OFFSET :index;"));
entryQuery.bindValue(QStringLiteral(":read"), false);
} else if (type == EpisodeModel::All) {
entryQuery.prepare(QStringLiteral("SELECT id FROM Entries ORDER BY updated DESC LIMIT 1 OFFSET :index;"));
} else if (type == EpisodeModel::Downloading) {
entryQuery.prepare(
QStringLiteral("SELECT * FROM Enclosures INNER JOIN Entries ON Enclosures.id = Entries.id WHERE downloaded=:downloaded ORDER BY updated DESC "
"LIMIT 1 OFFSET :index;"));
entryQuery.bindValue(QStringLiteral(":downloaded"), Enclosure::statusToDb(Enclosure::Downloading));
} else if (type == EpisodeModel::PartiallyDownloaded) {
entryQuery.prepare(
QStringLiteral("SELECT * FROM Enclosures INNER JOIN Entries ON Enclosures.id = Entries.id WHERE downloaded=:downloaded ORDER BY updated DESC "
"LIMIT 1 OFFSET :index;"));
entryQuery.bindValue(QStringLiteral(":downloaded"), Enclosure::statusToDb(Enclosure::PartiallyDownloaded));
} else if (type == EpisodeModel::Downloaded) {
entryQuery.prepare(
QStringLiteral("SELECT * FROM Enclosures INNER JOIN Entries ON Enclosures.id = Entries.id WHERE downloaded=:downloaded ORDER BY updated DESC "
"LIMIT 1 OFFSET :index;"));
entryQuery.bindValue(QStringLiteral(":downloaded"), Enclosure::statusToDb(Enclosure::Downloaded));
} else {
// this should not happen
qWarning() << "Cannot find entry type" << type << "in getEntry for entry index" << entry_index;
}
entryQuery.bindValue(QStringLiteral(":index"), entry_index);
Database::instance().execute(entryQuery);
if (!entryQuery.next()) {
qWarning() << "No element with index" << entry_index << "found";
return nullptr;
}
QString id = entryQuery.value(QStringLiteral("id")).toString();
return getEntry(id);
}
qWarning() << "Cannot find entry type" << type << "in getEntry for entry index" << entry_index;
return nullptr;
}
int DataManager::feedCount() const
{
return m_feedmap.count();
@ -207,41 +162,6 @@ int DataManager::entryCount(const Feed *feed) const
return m_entrymap[feed->url()].count();
}
int DataManager::entryCount(const EpisodeModel::Type type) const
{
QSqlQuery query;
if (type == EpisodeModel::All || type == EpisodeModel::New || type == EpisodeModel::Unread || type == EpisodeModel::Downloading
|| type == EpisodeModel::PartiallyDownloaded || type == EpisodeModel::Downloaded) {
if (type == EpisodeModel::New) {
query.prepare(QStringLiteral("SELECT COUNT (id) FROM Entries WHERE new=:new;"));
query.bindValue(QStringLiteral(":new"), true);
} else if (type == EpisodeModel::Unread) {
query.prepare(QStringLiteral("SELECT COUNT (id) FROM Entries WHERE read=:read;"));
query.bindValue(QStringLiteral(":read"), false);
} else if (type == EpisodeModel::All) {
query.prepare(QStringLiteral("SELECT COUNT (id) FROM Entries;"));
} else if (type == EpisodeModel::Downloading) {
query.prepare(QStringLiteral("SELECT COUNT (id) FROM Enclosures WHERE downloaded=:downloaded;"));
query.bindValue(QStringLiteral(":downloaded"), Enclosure::statusToDb(Enclosure::Downloading));
} else if (type == EpisodeModel::PartiallyDownloaded) {
query.prepare(QStringLiteral("SELECT COUNT (id) FROM Enclosures WHERE downloaded=:downloaded;"));
query.bindValue(QStringLiteral(":downloaded"), Enclosure::statusToDb(Enclosure::PartiallyDownloaded));
} else if (type == EpisodeModel::Downloaded) {
query.prepare(QStringLiteral("SELECT COUNT (id) FROM Enclosures WHERE downloaded=:downloaded;"));
query.bindValue(QStringLiteral(":downloaded"), Enclosure::statusToDb(Enclosure::Downloaded));
} else {
// this should not happen
qWarning() << "Cannot find entry type" << type << "in entryCount";
}
Database::instance().execute(query);
if (!query.next())
return -1;
return query.value(0).toInt();
}
qWarning() << "Cannot find entry type" << type << "in entryCount";
return -1;
}
int DataManager::unreadEntryCount(const Feed *feed) const
{
QSqlQuery query;

View File

@ -31,12 +31,10 @@ public:
Feed *getFeed(const QString &feedurl) const;
Entry *getEntry(const int feed_index, const int entry_index) const;
Entry *getEntry(const Feed *feed, const int entry_index) const;
Entry *getEntry(const EpisodeModel::Type type, const int entry_index) const;
Q_INVOKABLE Entry *getEntry(const QString &id) const;
int feedCount() const;
int entryCount(const int feed_index) const;
int entryCount(const Feed *feed) const;
int entryCount(const EpisodeModel::Type type) const;
int unreadEntryCount(const Feed *feed) const;
int newEntryCount(const Feed *feed) const;
Q_INVOKABLE void addFeed(const QString &url);

View File

@ -7,16 +7,15 @@
#include "models/downloadmodel.h"
#include "models/downloadmodellogging.h"
#include <QSqlQuery>
#include "database.h"
#include "datamanager.h"
#include "episodemodel.h"
DownloadModel::DownloadModel()
: QAbstractListModel(nullptr)
{
// initialize item counters
m_downloadingCount = DataManager::instance().entryCount(EpisodeModel::Downloading);
m_partiallyDownloadedCount = DataManager::instance().entryCount(EpisodeModel::PartiallyDownloaded);
m_downloadedCount = DataManager::instance().entryCount(EpisodeModel::Downloaded);
updateInternalState();
}
QVariant DownloadModel::data(const QModelIndex &index, int role) const
@ -24,11 +23,11 @@ QVariant DownloadModel::data(const QModelIndex &index, int role) const
if (role != 0)
return QVariant();
if (index.row() < m_downloadingCount) {
return QVariant::fromValue(DataManager::instance().getEntry(EpisodeModel::Downloading, index.row()));
return QVariant::fromValue(DataManager::instance().getEntry(m_downloadingIds[index.row()]));
} else if (index.row() < m_downloadingCount + m_partiallyDownloadedCount) {
return QVariant::fromValue(DataManager::instance().getEntry(EpisodeModel::PartiallyDownloaded, index.row() - m_downloadingCount));
return QVariant::fromValue(DataManager::instance().getEntry(m_partiallyDownloadedIds[index.row() - m_downloadingCount]));
} else if (index.row() < m_downloadingCount + m_partiallyDownloadedCount + m_downloadedCount) {
return QVariant::fromValue(DataManager::instance().getEntry(EpisodeModel::Downloaded, index.row() - m_downloadingCount - m_partiallyDownloadedCount));
return QVariant::fromValue(DataManager::instance().getEntry(m_downloadedIds[index.row() - m_downloadingCount - m_partiallyDownloadedCount]));
} else {
qWarning() << "Trying to fetch DownloadModel item outside of valid range; this should never happen";
return QVariant();
@ -52,10 +51,39 @@ int DownloadModel::rowCount(const QModelIndex &parent) const
void DownloadModel::monitorDownloadStatus()
{
beginResetModel();
m_downloadingCount = DataManager::instance().entryCount(EpisodeModel::Downloading);
m_partiallyDownloadedCount = DataManager::instance().entryCount(EpisodeModel::PartiallyDownloaded);
m_downloadedCount = DataManager::instance().entryCount(EpisodeModel::Downloaded);
updateInternalState();
endResetModel();
}
void DownloadModel::updateInternalState()
{
m_downloadingIds.clear();
m_partiallyDownloadedIds.clear();
m_downloadedIds.clear();
QSqlQuery query;
query.prepare(
QStringLiteral("SELECT * FROM Enclosures INNER JOIN Entries ON Enclosures.id = Entries.id WHERE downloaded=:downloaded ORDER BY updated DESC;"));
query.bindValue(QStringLiteral(":downloaded"), Enclosure::statusToDb(Enclosure::Downloading));
Database::instance().execute(query);
while (query.next()) {
m_downloadingIds += query.value(QStringLiteral("id")).toString();
}
query.bindValue(QStringLiteral(":downloaded"), Enclosure::statusToDb(Enclosure::PartiallyDownloaded));
Database::instance().execute(query);
while (query.next()) {
m_partiallyDownloadedIds += query.value(QStringLiteral("id")).toString();
}
query.bindValue(QStringLiteral(":downloaded"), Enclosure::statusToDb(Enclosure::Downloaded));
Database::instance().execute(query);
while (query.next()) {
m_downloadedIds += query.value(QStringLiteral("id")).toString();
}
m_downloadingCount = m_downloadingIds.count();
m_partiallyDownloadedCount = m_partiallyDownloadedIds.count();
m_downloadedCount = m_downloadedIds.count();
}

View File

@ -34,6 +34,13 @@ public Q_SLOTS:
private:
explicit DownloadModel();
void updateInternalState();
QStringList m_downloadingIds;
QStringList m_partiallyDownloadedIds;
QStringList m_downloadedIds;
int m_downloadingCount = 0;
int m_partiallyDownloadedCount = 0;
int m_downloadedCount = 0;

View File

@ -6,6 +6,9 @@
#include "models/episodemodel.h"
#include <QSqlQuery>
#include "database.h"
#include "datamanager.h"
#include "entry.h"
@ -18,15 +21,18 @@ EpisodeModel::EpisodeModel()
connect(&DataManager::instance(), &DataManager::feedEntriesUpdated, this, [this](const QString &url) {
Q_UNUSED(url)
beginResetModel();
updateInternalState();
endResetModel();
});
updateInternalState();
}
QVariant EpisodeModel::data(const QModelIndex &index, int role) const
{
if (role != 0)
return QVariant();
return QVariant::fromValue(DataManager::instance().getEntry(m_type, index.row()));
return QVariant::fromValue(DataManager::instance().getEntry(m_entryIds[index.row()]));
}
QHash<int, QByteArray> EpisodeModel::roleNames() const
@ -39,32 +45,16 @@ QHash<int, QByteArray> EpisodeModel::roleNames() const
int EpisodeModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return DataManager::instance().entryCount(m_type);
return m_entryIds.count();
}
EpisodeModel::Type EpisodeModel::type() const
void EpisodeModel::updateInternalState()
{
return m_type;
}
void EpisodeModel::setType(EpisodeModel::Type type)
{
m_type = type;
if (m_type == EpisodeModel::New) {
connect(&DataManager::instance(), &DataManager::newEntryCountChanged, this, [this](const QString &url) {
Q_UNUSED(url)
// we have to reset the entire model in case entries are removed or added
// because we have no way of knowing where those entries will be added/removed
beginResetModel();
endResetModel();
});
} else if (m_type == EpisodeModel::Unread) {
connect(&DataManager::instance(), &DataManager::unreadEntryCountChanged, this, [this](const QString &url) {
Q_UNUSED(url)
// we have to reset the entire model in case entries are removed or added
// because we have no way of knowing where those entries will be added/removed
beginResetModel();
endResetModel();
});
m_entryIds.clear();
QSqlQuery query;
query.prepare(QStringLiteral("SELECT id FROM Entries ORDER BY updated DESC;"));
Database::instance().execute(query);
while (query.next()) {
m_entryIds += query.value(QStringLiteral("id")).toString();
}
}

View File

@ -16,28 +16,13 @@ class EpisodeModel : public QAbstractListModel
Q_OBJECT
public:
enum Type {
All,
New,
Unread,
Downloading,
Downloaded,
PartiallyDownloaded,
};
Q_ENUM(Type)
Q_PROPERTY(Type type READ type WRITE setType)
explicit EpisodeModel();
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent) const override;
Type type() const;
public Q_SLOTS:
void setType(Type type);
private:
Type m_type = Type::All;
void updateInternalState();
QStringList m_entryIds;
};