mirror of https://github.com/KDE/kasts.git
A lot of progress on refactoring with DataManager
This commit is contained in:
parent
8035f0fd60
commit
13868709e7
|
@ -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();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -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);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
105
src/feed.cpp
105
src/feed.cpp
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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")));
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue