A lot of progress on refactoring with DataManager

This commit is contained in:
Bart De Vries 2021-04-02 22:31:34 +02:00
parent 8035f0fd60
commit 13868709e7
15 changed files with 371 additions and 207 deletions

View File

@ -119,86 +119,3 @@ void Database::cleanup()
// TODO: also delete enclosures and authors(?) // TODO: also delete enclosures and authors(?)
} }
} }
bool Database::feedExists(const QString &url)
{
QSqlQuery query;
query.prepare(QStringLiteral("SELECT COUNT (url) FROM Feeds WHERE url=:url;"));
query.bindValue(QStringLiteral(":url"), url);
Database::instance().execute(query);
query.next();
return query.value(0).toInt() != 0;
}
void Database::addFeed(const QString &url)
{
qDebug() << "Adding feed";
if (feedExists(url)) {
qDebug() << "Feed already exists";
return;
}
qDebug() << "Feed does not yet exist";
QUrl urlFromInput = QUrl::fromUserInput(url);
QSqlQuery query;
query.prepare(QStringLiteral("INSERT INTO Feeds VALUES (:name, :url, :image, :link, :description, :deleteAfterCount, :deleteAfterType, :subscribed, :lastUpdated, :notify);"));
query.bindValue(QStringLiteral(":name"), urlFromInput.toString());
query.bindValue(QStringLiteral(":url"), urlFromInput.toString());
query.bindValue(QStringLiteral(":image"), QLatin1String(""));
query.bindValue(QStringLiteral(":link"), QLatin1String(""));
query.bindValue(QStringLiteral(":description"), QLatin1String(""));
query.bindValue(QStringLiteral(":deleteAfterCount"), 0);
query.bindValue(QStringLiteral(":deleteAfterType"), 0);
query.bindValue(QStringLiteral(":subscribed"), QDateTime::currentDateTime().toSecsSinceEpoch());
query.bindValue(QStringLiteral(":lastUpdated"), 0);
query.bindValue(QStringLiteral(":notify"), false);
execute(query);
Q_EMIT feedAdded(urlFromInput.toString());
Fetcher::instance().fetch(urlFromInput.toString());
}
void Database::importFeeds(const QString &path)
{
QUrl url(path);
QFile file(url.isLocalFile() ? url.toLocalFile() : url.toString());
file.open(QIODevice::ReadOnly);
QXmlStreamReader xmlReader(&file);
while(!xmlReader.atEnd()) {
xmlReader.readNext();
if(xmlReader.tokenType() == 4 && xmlReader.attributes().hasAttribute(QStringLiteral("xmlUrl"))) {
addFeed(xmlReader.attributes().value(QStringLiteral("xmlUrl")).toString());
}
}
Fetcher::instance().fetchAll();
}
void Database::exportFeeds(const QString &path)
{
QUrl url(path);
QFile file(url.isLocalFile() ? url.toLocalFile() : url.toString());
file.open(QIODevice::WriteOnly);
QXmlStreamWriter xmlWriter(&file);
xmlWriter.setAutoFormatting(true);
xmlWriter.writeStartDocument(QStringLiteral("1.0"));
xmlWriter.writeStartElement(QStringLiteral("opml"));
xmlWriter.writeEmptyElement(QStringLiteral("head"));
xmlWriter.writeStartElement(QStringLiteral("body"));
xmlWriter.writeAttribute(QStringLiteral("version"), QStringLiteral("1.0"));
QSqlQuery query;
query.prepare(QStringLiteral("SELECT url, name FROM Feeds;"));
execute(query);
while(query.next()) {
xmlWriter.writeEmptyElement(QStringLiteral("outline"));
xmlWriter.writeAttribute(QStringLiteral("xmlUrl"), query.value(0).toString());
xmlWriter.writeAttribute(QStringLiteral("title"), query.value(1).toString());
}
xmlWriter.writeEndElement();
xmlWriter.writeEndElement();
xmlWriter.writeEndDocument();
}

View File

@ -20,12 +20,6 @@ public:
} }
bool execute(QSqlQuery &query); bool execute(QSqlQuery &query);
bool execute(const QString &query); bool execute(const QString &query);
Q_INVOKABLE void addFeed(const QString &url);
Q_INVOKABLE void importFeeds(const QString &path);
Q_INVOKABLE void exportFeeds(const QString &path);
Q_SIGNALS:
void feedAdded(const QString &url);
private: private:
Database(); Database();
@ -34,5 +28,4 @@ private:
bool migrate(); bool migrate();
bool migrateTo1(); bool migrateTo1();
void cleanup(); void cleanup();
bool feedExists(const QString &url);
}; };

View File

@ -4,16 +4,36 @@
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/ */
#include <QDateTime>
#include <QDir>
#include <QSqlDatabase>
#include <QSqlError>
#include <QStandardPaths>
#include <QUrl>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
#include "datamanager.h" #include "datamanager.h"
#include "fetcher.h" #include "fetcher.h"
#include "database.h" #include "database.h"
DataManager::DataManager() DataManager::DataManager()
{ {
// connect signals to lambda slots
connect(&Fetcher::instance(), &Fetcher::feedDetailsUpdated, this, [this](const QString &url, const QString &name, const QString &image, const QString &link, const QString &description, const QDateTime &lastUpdated) {
m_feeds[url]->setName(name);
m_feeds[url]->setImage(image);
m_feeds[url]->setLink(link);
m_feeds[url]->setDescription(description);
m_feeds[url]->setLastUpdated(lastUpdated);
// TODO: signal feedmodel: Q_EMIT dataChanged(createIndex(i, 0), createIndex(i, 0));
});
connect(&Fetcher::instance(), &Fetcher::feedUpdated, this, [this](const QString &url) {
// TODO: make DataManager rescan entries
Q_EMIT feedEntriesUpdated(url);
});
// Only read unique feedurls and entry ids from the database. // Only read unique feedurls and entry ids from the database.
// The feed and entry datastructres will be loaded lazily. // The feed and entry datastructures will be loaded lazily.
QSqlQuery query; QSqlQuery query;
query.prepare(QStringLiteral("SELECT url FROM Feeds;")); query.prepare(QStringLiteral("SELECT url FROM Feeds;"));
Database::instance().execute(query); Database::instance().execute(query);
@ -28,9 +48,9 @@ DataManager::DataManager()
Database::instance().execute(query); Database::instance().execute(query);
while (query.next()) { while (query.next()) {
m_entrymap[feedurl] += query.value(QStringLiteral("id")).toString(); m_entrymap[feedurl] += query.value(QStringLiteral("id")).toString();
}
qDebug() << m_entrymap[feedurl]; qDebug() << m_entrymap[feedurl];
} }
}
qDebug() << m_entrymap; qDebug() << m_entrymap;
} }
@ -46,7 +66,195 @@ Feed* DataManager::getFeed(QString const feedurl) const
return m_feeds[feedurl]; return m_feeds[feedurl];
} }
void DataManager::loadFeed(QString const feedurl) const
Entry* DataManager::getEntry(int const feed_index, int const entry_index) const
{
return getEntry(m_entrymap[m_feedmap[feed_index]][entry_index]);
}
Entry* DataManager::getEntry(const Feed* feed, int const entry_index) const
{
return getEntry(m_entrymap[feed->url()][entry_index]);
}
Entry* DataManager::getEntry(QString id) const
{
if (m_entries[id] == nullptr)
loadEntry(id);
return m_entries[id];
}
int DataManager::feedCount() const
{
return m_feedmap.count();
}
int DataManager::entryCount(const int feed_index) const
{
return m_entrymap[m_feedmap[feed_index]].count();
}
int DataManager::entryCount(const Feed* feed) const
{
return m_entrymap[feed->url()].count();
}
int DataManager::unreadEntryCount(const Feed* feed) const
{
QSqlQuery query;
query.prepare(QStringLiteral("SELECT COUNT (id) FROM Entries where feed=:feed AND read=0;"));
query.bindValue(QStringLiteral(":feed"), feed->url());
Database::instance().execute(query);
if (!query.next())
return -1;
return query.value(0).toInt();
}
void DataManager::removeFeed(const Feed* feed)
{
removeFeed(m_feedmap.indexOf(feed->url()));
}
void DataManager::removeFeed(const int &index)
{
// Get feed pointer
Feed* feed = m_feeds[m_feedmap[index]];
// First delete everything from the database
// Delete Authors
QSqlQuery query;
query.prepare(QStringLiteral("DELETE FROM Authors WHERE feed=:feed;"));
query.bindValue(QStringLiteral(":feed"), feed->url());
Database::instance().execute(query);
// Delete Entries
query.prepare(QStringLiteral("DELETE FROM Entries WHERE feed=:feed;"));
query.bindValue(QStringLiteral(":feed"), feed->url());
Database::instance().execute(query);
// Delete Enclosures
query.prepare(QStringLiteral("DELETE FROM Enclosures WHERE feed=:feed;"));
query.bindValue(QStringLiteral(":feed"), feed->url());
Database::instance().execute(query);
// Delete Feed
query.prepare(QStringLiteral("DELETE FROM Feeds WHERE url=:url;"));
query.bindValue(QStringLiteral(":url"), feed->url());
Database::instance().execute(query);
// Then delete the instances and mappings
for (auto& id : m_entrymap[feed->url()]) {
delete m_entries[id]; // delete pointer
m_entries.remove(id); // delete the hash key
}
m_entrymap.remove(feed->url()); // remove all the entry mappings belonging to the feed
delete feed; // remove the pointer
m_feeds.remove(m_feedmap[index]); // remove from m_feeds
m_feedmap.removeAt(index); // remove from m_feedmap
Q_EMIT(feedRemoved(index));
}
void DataManager::addFeed(const QString &url)
{
qDebug() << "Adding feed";
if (feedExists(url)) {
qDebug() << "Feed already exists";
return;
}
qDebug() << "Feed does not yet exist";
QUrl urlFromInput = QUrl::fromUserInput(url);
QSqlQuery query;
query.prepare(QStringLiteral("INSERT INTO Feeds VALUES (:name, :url, :image, :link, :description, :deleteAfterCount, :deleteAfterType, :subscribed, :lastUpdated, :notify);"));
query.bindValue(QStringLiteral(":name"), urlFromInput.toString());
query.bindValue(QStringLiteral(":url"), urlFromInput.toString());
query.bindValue(QStringLiteral(":image"), QLatin1String(""));
query.bindValue(QStringLiteral(":link"), QLatin1String(""));
query.bindValue(QStringLiteral(":description"), QLatin1String(""));
query.bindValue(QStringLiteral(":deleteAfterCount"), 0);
query.bindValue(QStringLiteral(":deleteAfterType"), 0);
query.bindValue(QStringLiteral(":subscribed"), QDateTime::currentDateTime().toSecsSinceEpoch());
query.bindValue(QStringLiteral(":lastUpdated"), 0);
query.bindValue(QStringLiteral(":notify"), false);
Database::instance().execute(query);
m_feeds[urlFromInput.toString()] = new Feed(urlFromInput.toString());
m_feedmap.append(urlFromInput.toString());
Q_EMIT feedAdded(urlFromInput.toString());
Fetcher::instance().fetch(urlFromInput.toString());
}
void DataManager::importFeeds(const QString &path)
{
QUrl url(path);
QFile file(url.isLocalFile() ? url.toLocalFile() : url.toString());
file.open(QIODevice::ReadOnly);
QXmlStreamReader xmlReader(&file);
while(!xmlReader.atEnd()) {
xmlReader.readNext();
if(xmlReader.tokenType() == 4 && xmlReader.attributes().hasAttribute(QStringLiteral("xmlUrl"))) {
addFeed(xmlReader.attributes().value(QStringLiteral("xmlUrl")).toString());
}
}
Fetcher::instance().fetchAll();
}
void DataManager::exportFeeds(const QString &path)
{
QUrl url(path);
QFile file(url.isLocalFile() ? url.toLocalFile() : url.toString());
file.open(QIODevice::WriteOnly);
QXmlStreamWriter xmlWriter(&file);
xmlWriter.setAutoFormatting(true);
xmlWriter.writeStartDocument(QStringLiteral("1.0"));
xmlWriter.writeStartElement(QStringLiteral("opml"));
xmlWriter.writeEmptyElement(QStringLiteral("head"));
xmlWriter.writeStartElement(QStringLiteral("body"));
xmlWriter.writeAttribute(QStringLiteral("version"), QStringLiteral("1.0"));
QSqlQuery query;
query.prepare(QStringLiteral("SELECT url, name FROM Feeds;"));
Database::instance().execute(query);
while(query.next()) {
xmlWriter.writeEmptyElement(QStringLiteral("outline"));
xmlWriter.writeAttribute(QStringLiteral("xmlUrl"), query.value(0).toString());
xmlWriter.writeAttribute(QStringLiteral("title"), query.value(1).toString());
}
xmlWriter.writeEndElement();
xmlWriter.writeEndElement();
xmlWriter.writeEndDocument();
}
void DataManager::loadFeed(const QString feedurl) const
{ {
m_feeds[feedurl] = new Feed(feedurl); m_feeds[feedurl] = new Feed(feedurl);
} }
void DataManager::loadEntry(const QString id) const
{
// First find the feed that this entry belongs to
Feed* feed = nullptr;
QHashIterator<QString, QStringList> i(m_entrymap);
while (i.hasNext()) {
i.next();
if (i.value().contains(id))
feed = getFeed(i.key());
}
if (feed == nullptr) {
qDebug() << "Failed to find feed belonging to entry" << id;
return;
}
m_entries[id] = new Entry(feed, id);
}
bool DataManager::feedExists(const QString &url)
{
return m_feeds.contains(url);
}

View File

@ -22,13 +22,39 @@ public:
Feed* getFeed(int const index) const; Feed* getFeed(int const index) const;
Feed* getFeed(QString const feedurl) const; Feed* getFeed(QString const feedurl) const;
Entry* getEntry(int const feed_index, int const entry_index) const;
Entry* getEntry(const Feed* feed, int const entry_index) const;
Entry* getEntry(const QString id) const;
int feedCount() const;
int entryCount(const int feed_index) const;
int entryCount(const Feed* feed) const;
int unreadEntryCount(const Feed* feed) const;
Q_INVOKABLE void addFeed(const QString &url);
Q_INVOKABLE void removeFeed(const Feed* feed);
Q_INVOKABLE void removeFeed(const int &index);
//Q_INVOKABLE void addEntry(const QString &url);
//Q_INVOKABLE void removeEntry(const QString &url);
//Q_INVOKABLE void removeEntry(const Feed* feed, const int &index);
Q_INVOKABLE void importFeeds(const QString &path);
Q_INVOKABLE void exportFeeds(const QString &path);
Q_SIGNALS:
void feedAdded(const QString &url);
void feedRemoved(const int &index);
void entryAdded(const QString &id);
void entryRemoved(const Feed*, const int &index);
void feedEntriesUpdated(const QString &url);
private: private:
DataManager(); DataManager();
void loadFeed(QString feedurl) const; void loadFeed(QString feedurl) const;
void loadEntry(QString id) const;
bool feedExists(const QString &url);
QVector<QString> m_feedmap; QStringList m_feedmap;
mutable QHash<QString, Feed*> m_feeds; mutable QHash<QString, Feed*> m_feeds;
QHash<QString, QVector<QString> > m_entrymap; QHash<QString, QStringList> m_entrymap;
mutable QHash<QString, Entry*> m_entries; mutable QHash<QString, Entry*> m_entries;
}; };

View File

@ -9,6 +9,7 @@
#include <QVector> #include <QVector>
#include "database.h" #include "database.h"
#include "datamanager.h"
#include "entriesmodel.h" #include "entriesmodel.h"
#include "fetcher.h" #include "fetcher.h"
@ -16,30 +17,20 @@ EntriesModel::EntriesModel(Feed *feed)
: QAbstractListModel(feed) : QAbstractListModel(feed)
, m_feed(feed) , m_feed(feed)
{ {
connect(&Fetcher::instance(), &Fetcher::feedUpdated, this, [this](const QString &url) { connect(&DataManager::instance(), &DataManager::feedEntriesUpdated, this, [this](const QString &url) {
if (m_feed->url() == url) { if (m_feed->url() == url) {
beginResetModel(); beginResetModel();
for (auto &entry : m_entries) { // TODO: make sure to pop the entrylistpage if it's the active one
delete entry;
}
m_entries.clear();
endResetModel(); endResetModel();
} }
}); });
} }
EntriesModel::~EntriesModel()
{
qDeleteAll(m_entries);
}
QVariant EntriesModel::data(const QModelIndex &index, int role) const QVariant EntriesModel::data(const QModelIndex &index, int role) const
{ {
if (role != 0) if (role != 0)
return QVariant(); return QVariant();
if (m_entries[index.row()] == nullptr) return QVariant::fromValue(DataManager::instance().getEntry(m_feed, index.row()));
loadEntry(index.row());
return QVariant::fromValue(m_entries[index.row()]);
} }
QHash<int, QByteArray> EntriesModel::roleNames() const QHash<int, QByteArray> EntriesModel::roleNames() const
@ -52,18 +43,7 @@ QHash<int, QByteArray> EntriesModel::roleNames() const
int EntriesModel::rowCount(const QModelIndex &parent) const int EntriesModel::rowCount(const QModelIndex &parent) const
{ {
Q_UNUSED(parent) Q_UNUSED(parent)
QSqlQuery query; return DataManager::instance().entryCount(m_feed);
query.prepare(QStringLiteral("SELECT COUNT() FROM Entries WHERE feed=:feed;"));
query.bindValue(QStringLiteral(":feed"), m_feed->url());
Database::instance().execute(query);
if (!query.next())
qWarning() << "Failed to query feed count";
return query.value(0).toInt();
}
void EntriesModel::loadEntry(int index) const
{
m_entries[index] = new Entry(m_feed, index);
} }
Feed *EntriesModel::feed() const Feed *EntriesModel::feed() const

View File

@ -22,7 +22,6 @@ class EntriesModel : public QAbstractListModel
public: public:
explicit EntriesModel(Feed *feed); explicit EntriesModel(Feed *feed);
~EntriesModel() override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent) const override; int rowCount(const QModelIndex &parent) const override;
@ -30,8 +29,5 @@ public:
Feed *feed() const; Feed *feed() const;
private: private:
void loadEntry(int index) const;
Feed *m_feed; Feed *m_feed;
mutable QHash<int, Entry *> m_entries;
}; };

View File

@ -47,6 +47,41 @@ Entry::Entry(Feed *feed, int index)
} }
} }
Entry::Entry(Feed *feed, QString id)
: QObject(nullptr)
, m_feed(feed)
{
QSqlQuery entryQuery;
entryQuery.prepare(QStringLiteral("SELECT * FROM Entries WHERE feed=:feed AND id=:id;"));
entryQuery.bindValue(QStringLiteral(":feed"), m_feed->url());
entryQuery.bindValue(QStringLiteral(":id"), id);
Database::instance().execute(entryQuery);
if (!entryQuery.next())
qWarning() << "No element with index" << id << "found in feed" << m_feed->url();
QSqlQuery authorQuery;
authorQuery.prepare(QStringLiteral("SELECT * FROM Authors WHERE id=:id"));
authorQuery.bindValue(QStringLiteral(":id"), entryQuery.value(QStringLiteral("id")).toString());
Database::instance().execute(authorQuery);
while (authorQuery.next()) {
m_authors += new Author(authorQuery.value(QStringLiteral("name")).toString(), authorQuery.value(QStringLiteral("email")).toString(), authorQuery.value(QStringLiteral("uri")).toString(), nullptr);
}
m_created.setSecsSinceEpoch(entryQuery.value(QStringLiteral("created")).toInt());
m_updated.setSecsSinceEpoch(entryQuery.value(QStringLiteral("updated")).toInt());
m_id = entryQuery.value(QStringLiteral("id")).toString();
m_title = entryQuery.value(QStringLiteral("title")).toString();
m_content = entryQuery.value(QStringLiteral("content")).toString();
m_link = entryQuery.value(QStringLiteral("link")).toString();
m_read = entryQuery.value(QStringLiteral("read")).toBool();
if (entryQuery.value(QStringLiteral("hasEnclosure")).toBool()) {
m_enclosure = new Enclosure(this);
}
}
Entry::~Entry() Entry::~Entry()
{ {
qDeleteAll(m_authors); qDeleteAll(m_authors);

View File

@ -35,6 +35,7 @@ class Entry : public QObject
public: public:
Entry(Feed *feed, int index); Entry(Feed *feed, int index);
Entry(Feed *feed, QString id);
~Entry(); ~Entry();
QString id() const; QString id() const;

View File

@ -7,6 +7,7 @@
#include <QVariant> #include <QVariant>
#include "database.h" #include "database.h"
#include "datamanager.h"
#include "entriesmodel.h" #include "entriesmodel.h"
#include "feed.h" #include "feed.h"
#include "fetcher.h" #include "fetcher.h"
@ -78,6 +79,73 @@ Feed::Feed(int index)
m_entries = new EntriesModel(this); m_entries = new EntriesModel(this);
} }
Feed::Feed(QString const feedurl)
: QObject(nullptr)
{
QSqlQuery query;
query.prepare(QStringLiteral("SELECT * FROM Feeds WHERE url=:feedurl;"));
query.bindValue(QStringLiteral(":feedurl"), feedurl);
Database::instance().execute(query);
if (!query.next())
qWarning() << "Failed to load feed" << feedurl;
QSqlQuery authorQuery;
authorQuery.prepare(QStringLiteral("SELECT * FROM Authors WHERE id='' AND feed=:feed"));
authorQuery.bindValue(QStringLiteral(":feed"), feedurl);
Database::instance().execute(authorQuery);
while (authorQuery.next()) {
m_authors += new Author(authorQuery.value(QStringLiteral("name")).toString(), authorQuery.value(QStringLiteral("email")).toString(), authorQuery.value(QStringLiteral("uri")).toString(), nullptr);
}
m_subscribed.setSecsSinceEpoch(query.value(QStringLiteral("subscribed")).toInt());
m_lastUpdated.setSecsSinceEpoch(query.value(QStringLiteral("lastUpdated")).toInt());
m_url = query.value(QStringLiteral("url")).toString();
m_name = query.value(QStringLiteral("name")).toString();
m_image = query.value(QStringLiteral("image")).toString();
m_link = query.value(QStringLiteral("link")).toString();
m_description = query.value(QStringLiteral("description")).toString();
m_deleteAfterCount = query.value(QStringLiteral("deleteAfterCount")).toInt();
m_deleteAfterType = query.value(QStringLiteral("deleteAfterType")).toInt();
m_notify = query.value(QStringLiteral("notify")).toBool();
m_errorId = 0;
m_errorString = QLatin1String("");
connect(&Fetcher::instance(), &Fetcher::startedFetchingFeed, this, [this](const QString &url) {
if (url == m_url) {
m_errorId = 0;
m_errorString = QLatin1String("");
setRefreshing(true);
}
});
connect(&Fetcher::instance(), &Fetcher::feedUpdated, this, [this](const QString &url) {
if (url == m_url) {
setRefreshing(false);
Q_EMIT entryCountChanged();
Q_EMIT unreadEntryCountChanged();
setErrorId(0);
setErrorString(QLatin1String(""));
}
});
connect(&Fetcher::instance(), &Fetcher::error, this, [this](const QString &url, int errorId, const QString &errorString) {
if(url == m_url) {
setErrorId(errorId);
setErrorString(errorString);
setRefreshing(false);
}
});
connect(&Fetcher::instance(), &Fetcher::downloadFinished, this, [this](QString url) {
if(url == m_image)
Q_EMIT imageChanged(url);
});
m_entries = new EntriesModel(this);
}
Feed::~Feed() Feed::~Feed()
{ {
} }
@ -139,24 +207,12 @@ bool Feed::notify() const
int Feed::entryCount() const int Feed::entryCount() const
{ {
QSqlQuery query; return DataManager::instance().entryCount(this);
query.prepare(QStringLiteral("SELECT COUNT (id) FROM Entries where feed=:feed;"));
query.bindValue(QStringLiteral(":feed"), m_url);
Database::instance().execute(query);
if (!query.next())
return -1;
return query.value(0).toInt();
} }
int Feed::unreadEntryCount() const int Feed::unreadEntryCount() const
{ {
QSqlQuery query; return DataManager::instance().unreadEntryCount(this);
query.prepare(QStringLiteral("SELECT COUNT (id) FROM Entries where feed=:feed AND read=0;"));
query.bindValue(QStringLiteral(":feed"), m_url);
Database::instance().execute(query);
if (!query.next())
return -1;
return query.value(0).toInt();
} }
bool Feed::refreshing() const bool Feed::refreshing() const
@ -250,24 +306,3 @@ void Feed::refresh()
{ {
Fetcher::instance().fetch(m_url); Fetcher::instance().fetch(m_url);
} }
void Feed::remove()
{
// Delete Authors
QSqlQuery query;
query.prepare(QStringLiteral("DELETE FROM Authors WHERE feed=:feed;"));
query.bindValue(QStringLiteral(":feed"), m_url);
Database::instance().execute(query);
// Delete Entries
query.prepare(QStringLiteral("DELETE FROM Entries WHERE feed=:feed;"));
query.bindValue(QStringLiteral(":feed"), m_url);
Database::instance().execute(query);
// TODO Delete Enclosures
// Delete Feed
query.prepare(QStringLiteral("DELETE FROM Feeds WHERE url=:url;"));
query.bindValue(QStringLiteral(":url"), m_url);
Database::instance().execute(query);
}

View File

@ -38,6 +38,7 @@ class Feed : public QObject
public: public:
Feed(int index); Feed(int index);
Feed(QString const feedurl);
~Feed(); ~Feed();
@ -74,7 +75,6 @@ public:
void setErrorString(const QString &errorString); void setErrorString(const QString &errorString);
Q_INVOKABLE void refresh(); Q_INVOKABLE void refresh();
void remove();
Q_SIGNALS: Q_SIGNALS:
void nameChanged(const QString &name); void nameChanged(const QString &name);

View File

@ -11,28 +11,20 @@
#include <QVariant> #include <QVariant>
#include "database.h" #include "database.h"
#include "datamanager.h"
#include "feedsmodel.h" #include "feedsmodel.h"
#include "fetcher.h" #include "fetcher.h"
FeedsModel::FeedsModel(QObject *parent) FeedsModel::FeedsModel(QObject *parent)
: QAbstractListModel(parent) : QAbstractListModel(parent)
{ {
connect(&Database::instance(), &Database::feedAdded, this, [this]() { connect(&DataManager::instance(), &DataManager::feedAdded, this, [this]() {
beginInsertRows(QModelIndex(), rowCount(QModelIndex()) - 1, rowCount(QModelIndex()) - 1); beginInsertRows(QModelIndex(), rowCount(QModelIndex()) - 1, rowCount(QModelIndex()) - 1);
endInsertRows(); endInsertRows();
}); });
connect(&Fetcher::instance(), &Fetcher::feedDetailsUpdated, this, [this](const QString &url, const QString &name, const QString &image, const QString &link, const QString &description, const QDateTime &lastUpdated) { connect(&DataManager::instance(), &DataManager::feedRemoved, this, [this](const int &index) {
for (int i = 0; i < m_feeds.length(); i++) { beginRemoveRows(QModelIndex(), index, index);
if (m_feeds[i]->url() == url) { endRemoveRows();
m_feeds[i]->setName(name);
m_feeds[i]->setImage(image);
m_feeds[i]->setLink(link);
m_feeds[i]->setDescription(description);
m_feeds[i]->setLastUpdated(lastUpdated);
Q_EMIT dataChanged(createIndex(i, 0), createIndex(i, 0));
break;
}
}
}); });
} }
@ -45,41 +37,25 @@ QHash<int, QByteArray> FeedsModel::roleNames() const
int FeedsModel::rowCount(const QModelIndex &parent) const int FeedsModel::rowCount(const QModelIndex &parent) const
{ {
Q_UNUSED(parent) Q_UNUSED(parent);
QSqlQuery query; return DataManager::instance().feedCount();
query.prepare(QStringLiteral("SELECT COUNT() FROM Feeds;"));
Database::instance().execute(query);
if (!query.next())
qWarning() << "Failed to query feed count";
return query.value(0).toInt();
} }
QVariant FeedsModel::data(const QModelIndex &index, int role) const QVariant FeedsModel::data(const QModelIndex &index, int role) const
{ {
if (role != 0) if (role != 0)
return QVariant(); return QVariant();
if (m_feeds.length() <= index.row()) return QVariant::fromValue(DataManager::instance().getFeed(index.row()));
loadFeed(index.row());
return QVariant::fromValue(m_feeds[index.row()]);
}
void FeedsModel::loadFeed(int index) const
{
m_feeds += new Feed(index);
} }
void FeedsModel::removeFeed(int index) void FeedsModel::removeFeed(int index)
{ {
m_feeds[index]->remove(); DataManager::instance().removeFeed(index);
delete m_feeds[index];
beginRemoveRows(QModelIndex(), index, index);
m_feeds.removeAt(index);
endRemoveRows();
} }
void FeedsModel::refreshAll() void FeedsModel::refreshAll()
{ {
for (auto &feed : m_feeds) { // for (auto &feed : m_feeds) {
feed->refresh(); // feed->refresh();
} // }
} }

View File

@ -24,9 +24,4 @@ public:
int rowCount(const QModelIndex &parent) const override; int rowCount(const QModelIndex &parent) const override;
Q_INVOKABLE void removeFeed(int index); Q_INVOKABLE void removeFeed(int index);
Q_INVOKABLE void refreshAll(); Q_INVOKABLE void refreshAll();
private:
void loadFeed(int index) const;
mutable QVector<Feed *> m_feeds;
}; };

View File

@ -89,6 +89,8 @@ int main(int argc, char *argv[])
Database::instance(); Database::instance();
DataManager::instance(); DataManager::instance();
//qDebug() << DataManager::instance().getFeed(0)->name();
//qDebug() << DataManager::instance().getEntry(0, 0)->title();
engine.load(QUrl(QStringLiteral("qrc:///main.qml"))); engine.load(QUrl(QStringLiteral("qrc:///main.qml")));

View File

@ -34,7 +34,7 @@ Kirigami.OverlaySheet {
text: i18n("Add Feed") text: i18n("Add Feed")
enabled: urlField.text enabled: urlField.text
onClicked: { onClicked: {
Database.addFeed(urlField.text) DataManager.addFeed(urlField.text)
addSheet.close() addSheet.close()
} }
} }

View File

@ -81,7 +81,7 @@ Kirigami.ScrollablePage {
title: i18n("Import Feeds") title: i18n("Import Feeds")
folder: StandardPaths.writableLocation(StandardPaths.HomeLocation) folder: StandardPaths.writableLocation(StandardPaths.HomeLocation)
nameFilters: [i18n("All Files (*)"), i18n("XML Files (*.xml)"), i18n("OPML Files (*.opml)")] nameFilters: [i18n("All Files (*)"), i18n("XML Files (*.xml)"), i18n("OPML Files (*.opml)")]
onAccepted: Database.importFeeds(file) onAccepted: DataManager.importFeeds(file)
} }
FileDialog { FileDialog {
@ -89,7 +89,7 @@ Kirigami.ScrollablePage {
title: i18n("Export Feeds") title: i18n("Export Feeds")
folder: StandardPaths.writableLocation(StandardPaths.HomeLocation) folder: StandardPaths.writableLocation(StandardPaths.HomeLocation)
nameFilters: [i18n("All Files")] nameFilters: [i18n("All Files")]
onAccepted: Database.exportFeeds(file) onAccepted: DataManager.exportFeeds(file)
fileMode: FileDialog.SaveFile fileMode: FileDialog.SaveFile
} }
} }