Refactor models

This commit is contained in:
Tobias Fella 2020-05-26 16:32:07 +02:00
parent 8a05a47e40
commit 8269cb960f
19 changed files with 585 additions and 183 deletions

View File

@ -4,6 +4,9 @@ set(alligator_SRCS
entryListModel.cpp entryListModel.cpp
fetcher.cpp fetcher.cpp
database.cpp database.cpp
entry.cpp
feed.cpp
author.cpp
resources.qrc resources.qrc
) )

48
src/author.cpp Normal file
View File

@ -0,0 +1,48 @@
/**
* Copyright 2020 Tobias Fella <fella@posteo.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "author.h"
Author::Author(QString name, QString email, QString url, QObject *parent)
: QObject(parent)
, m_name(name)
, m_email(email)
, m_url(url)
{
}
Author::~Author()
{
}
QString Author::name() const
{
return m_name;
}
QString Author::email() const
{
return m_email;
}
QString Author::url() const
{
return m_url;
}

48
src/author.h Normal file
View File

@ -0,0 +1,48 @@
/**
* Copyright 2020 Tobias Fella <fella@posteo.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef AUTHOR_H
#define AUTHOR_H
#include <QObject>
class Author : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name CONSTANT)
Q_PROPERTY(QString email READ email CONSTANT)
Q_PROPERTY(QString url READ url CONSTANT)
public:
Author(QString name, QString email, QString url, QObject *parent = nullptr);
~Author();
QString name() const;
QString email() const;
QString url() const;
private:
QString m_name;
QString m_email;
QString m_url;
};
#endif // AUTHOR_H

View File

@ -151,5 +151,7 @@ void Database::addFeed(QString url)
query.bindValue(QStringLiteral(":image"), QLatin1String("")); query.bindValue(QStringLiteral(":image"), QLatin1String(""));
execute(query); execute(query);
Fetcher::instance().fetch(QUrl(url)); Q_EMIT feedAdded(url);
Fetcher::instance().fetch(url);
} }

View File

@ -22,8 +22,10 @@
#include <QSqlQuery> #include <QSqlQuery>
class Database class Database : public QObject
{ {
Q_OBJECT
public: public:
static Database &instance() static Database &instance()
{ {
@ -32,7 +34,10 @@ public:
} }
bool execute(QSqlQuery &query); bool execute(QSqlQuery &query);
bool execute(QString query); bool execute(QString query);
void addFeed(QString url); Q_INVOKABLE void addFeed(QString url);
Q_SIGNALS:
void feedAdded(QString url);
private: private:
Database(); Database();

72
src/entry.cpp Normal file
View File

@ -0,0 +1,72 @@
/**
* Copyright 2020 Tobias Fella <fella@posteo.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "entry.h"
#include <QUrl>
Entry::Entry(QString title, QString content, QVector<Author *> authors, QDateTime created, QDateTime updated, QString link, QObject *parent)
: QObject(parent)
, m_title(title)
, m_content(content)
, m_authors(authors)
, m_created(created)
, m_updated(updated)
, m_link(link)
{
}
Entry::~Entry()
{
}
QString Entry::title() const
{
return m_title;
}
QString Entry::content() const
{
return m_content;
}
QVector<Author *> Entry::authors() const
{
return m_authors;
}
QDateTime Entry::created() const
{
return m_created;
}
QDateTime Entry::updated() const
{
return m_updated;
}
QString Entry::link() const
{
return m_link;
}
QString Entry::baseUrl() const
{
return QUrl(m_link).adjusted(QUrl::RemovePath).toString();
}

67
src/entry.h Normal file
View File

@ -0,0 +1,67 @@
/**
* Copyright 2020 Tobias Fella <fella@posteo.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef ENTRY_H
#define ENTRY_H
#include <QDateTime>
#include <QDebug>
#include <QObject>
#include <QString>
#include <QStringList>
#include "author.h"
#include "feed.h"
class Entry : public QObject
{
Q_OBJECT
Q_PROPERTY(QString title READ title CONSTANT)
Q_PROPERTY(QString content READ content CONSTANT)
Q_PROPERTY(QVector<Author *> authors READ authors CONSTANT)
Q_PROPERTY(QDateTime created READ created CONSTANT)
Q_PROPERTY(QDateTime updated READ updated CONSTANT)
Q_PROPERTY(QString link READ link CONSTANT)
Q_PROPERTY(QString baseUrl READ baseUrl CONSTANT)
public:
Entry(QString title, QString content, QVector<Author *> authors, QDateTime created, QDateTime updated, QString link, QObject *parent = nullptr);
~Entry();
QString title() const;
QString content() const;
QVector<Author *> authors() const;
QDateTime created() const;
QDateTime updated() const;
QString link() const;
QString baseUrl() const;
private:
Feed *m_feed;
QString m_title;
QString m_content;
QVector<Author *> m_authors;
QDateTime m_created;
QDateTime m_updated;
QString m_link;
};
#endif // ENTRY_H

View File

@ -18,8 +18,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include <QDateTime> #include <QAbstractListModel>
#include <QSqlQuery> #include <QVariant>
#include <QVector> #include <QVector>
#include "database.h" #include "database.h"
@ -27,84 +27,90 @@
#include "fetcher.h" #include "fetcher.h"
EntryListModel::EntryListModel(QObject *parent) EntryListModel::EntryListModel(QObject *parent)
: QSqlTableModel(parent) : QAbstractListModel(parent)
{ {
setTable(QStringLiteral("entries")); connect(&Fetcher::instance(), &Fetcher::feedUpdated, this, [this](QString url) {
setSort(Updated, Qt::DescendingOrder); if (m_feed->url() == url) {
setEditStrategy(OnFieldChange); beginResetModel();
select(); for (auto &entry : m_entries) {
delete entry;
connect(&Fetcher::instance(), &Fetcher::updated, this, [this]() { select(); }); }
m_entries.clear();
endResetModel();
}
});
connect(&Fetcher::instance(), &Fetcher::feedDetailsUpdated, this, [this](QString url, QString name, QString image) {
if (m_feed->url() == url) {
m_feed->setName(name);
m_feed->setImage(image);
}
});
} }
QVariant EntryListModel::data(const QModelIndex &index, int role) const QVariant EntryListModel::data(const QModelIndex &index, int role) const
{ {
if (role == Enclosure) { if (role != 0)
return enclosure(data(index, Id).toString()); return QVariant();
} if (m_entries[index.row()] == nullptr)
if (role == Authors) { loadEntry(index.row());
QSqlQuery query; return QVariant::fromValue(m_entries[index.row()]);
query.prepare(QStringLiteral("SELECT name FROM Authors WHERE id=:id"));
query.bindValue(QStringLiteral(":id"), data(index, Id));
Database::instance().execute(query);
QStringList authors;
while (query.next()) {
authors += query.value(0).toString();
}
return authors;
}
if (role == Updated || role == Created) {
QDateTime updated;
updated.setSecsSinceEpoch(QSqlTableModel::data(createIndex(index.row(), role), 0).toInt());
return updated;
}
return QSqlTableModel::data(createIndex(index.row(), role), 0);
}
QString EntryListModel::enclosure(QString id) const
{
QSqlQuery query;
query.prepare(QStringLiteral("SELECT url from Enclosures WHERE id=:id;"));
query.bindValue(QStringLiteral(":id"), id);
Database::instance().execute(query);
return query.next() ? query.value(0).toString() : QLatin1String("");
} }
QHash<int, QByteArray> EntryListModel::roleNames() const QHash<int, QByteArray> EntryListModel::roleNames() const
{ {
QHash<int, QByteArray> roleNames; QHash<int, QByteArray> roleNames;
roleNames[Feed] = "feed"; roleNames[0] = "entry";
roleNames[Id] = "id";
roleNames[Title] = "title";
roleNames[Content] = "content";
roleNames[Created] = "created";
roleNames[Updated] = "updated";
roleNames[Link] = "link";
roleNames[Authors] = "authors";
roleNames[Enclosure] = "enclosure";
return roleNames; return roleNames;
} }
void EntryListModel::setFeed(QString url) int EntryListModel::rowCount(const QModelIndex &parent) const
{ {
m_feed = url; Q_UNUSED(parent)
setFilter(QStringLiteral("feed ='%1'").arg(url)); QSqlQuery query;
select(); query.prepare(QStringLiteral("SELECT COUNT() FROM Entries WHERE feed=:feed;"));
emit feedChanged(url); 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();
} }
QString EntryListModel::feed() const void EntryListModel::loadEntry(int index) const
{
QSqlQuery entryQuery;
entryQuery.prepare(QStringLiteral("SELECT * FROM Entries WHERE feed=:feed ORDER BY updated DESC LIMIT 1 OFFSET :index;"));
entryQuery.bindValue(QStringLiteral(":feed"), m_feed->url());
entryQuery.bindValue(QStringLiteral(":index"), index);
Database::instance().execute(entryQuery);
if (!entryQuery.next())
qWarning() << "No element with index" << index << "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);
QVector<Author *> authors;
while (authorQuery.next()) {
authors += new Author(authorQuery.value(QStringLiteral("name")).toString(), authorQuery.value(QStringLiteral("email")).toString(), authorQuery.value(QStringLiteral("uri")).toString(), nullptr);
}
QDateTime created;
created.setSecsSinceEpoch(entryQuery.value(QStringLiteral("created")).toInt());
QDateTime updated;
updated.setSecsSinceEpoch(entryQuery.value(QStringLiteral("updated")).toInt());
Entry *entry = new Entry(entryQuery.value(QStringLiteral("title")).toString(), entryQuery.value(QStringLiteral("content")).toString(), authors, created, updated, entryQuery.value(QStringLiteral("link")).toString(), nullptr);
m_entries[index] = entry;
}
Feed *EntryListModel::feed() const
{ {
return m_feed; return m_feed;
} }
void EntryListModel::fetch() void EntryListModel::setFeed(Feed *feed)
{ {
Fetcher::instance().fetch(QUrl(m_feed)); m_feed = feed;
} emit feedChanged(feed);
QString EntryListModel::baseUrl(QString url)
{
return QUrl(url).adjusted(QUrl::RemovePath).toString();
} }

View File

@ -20,41 +20,35 @@
#pragma once #pragma once
#include <QAbstractListModel>
#include <QHash>
#include <QObject> #include <QObject>
#include <QSqlTableModel>
#include <QString> #include <QString>
class EntryListModel : public QSqlTableModel #include "entry.h"
class EntryListModel : public QAbstractListModel
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QString feed READ feed WRITE setFeed NOTIFY feedChanged)
Q_PROPERTY(Feed *feed READ feed WRITE setFeed NOTIFY feedChanged)
public: public:
enum DataRole {
Feed = 0,
Id,
Title,
Content,
Created,
Updated,
Link,
Authors,
Enclosure,
};
explicit EntryListModel(QObject *parent = nullptr); explicit EntryListModel(QObject *parent = nullptr);
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;
Q_INVOKABLE void fetch(); Feed *feed() const;
Q_INVOKABLE QString baseUrl(QString url);
QString feed() const; void setFeed(Feed *feed);
void setFeed(QString feed);
Q_SIGNALS: Q_SIGNALS:
void feedChanged(QString feed); void feedChanged(Feed *feed);
private: private:
QString m_feed; void loadEntry(int index) const;
QString enclosure(QString id) const; Feed *m_feed;
mutable QHash<int, Entry *> m_entries;
}; };

84
src/feed.cpp Normal file
View File

@ -0,0 +1,84 @@
/*
* Copyright 2020 Tobias Fella <fella@posteo.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <QVariant>
#include "database.h"
#include "feed.h"
Feed::Feed(QString url, QString name, QString image, QObject *parent)
: QObject(parent)
, m_url(url)
, m_name(name)
, m_image(image)
{
}
Feed::~Feed()
{
}
QString Feed::url() const
{
return m_url;
}
QString Feed::name() const
{
return m_name;
}
QString Feed::image() const
{
return m_image;
}
void Feed::setName(QString name)
{
m_name = name;
emit nameChanged(m_name);
}
void Feed::setImage(QString image)
{
m_image = image;
emit imageChanged(m_image);
}
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);
}

58
src/feed.h Normal file
View File

@ -0,0 +1,58 @@
/*
* Copyright 2020 Tobias Fella <fella@posteo.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef FEED_H
#define FEED_H
#include <QObject>
class Feed : public QObject
{
Q_OBJECT
Q_PROPERTY(QString url READ url CONSTANT)
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
Q_PROPERTY(QString image READ image WRITE setImage NOTIFY imageChanged)
public:
Feed(QString url, QString name, QString image, QObject *parent = nullptr);
~Feed();
QString url() const;
QString name() const;
QString image() const;
void setName(QString name);
void setImage(QString image);
void remove();
Q_SIGNALS:
void nameChanged(QString &name);
void imageChanged(QString &image);
private:
QString m_url;
QString m_name;
QString m_image;
};
#endif // FEED_H

View File

@ -20,62 +20,78 @@
#include <QDebug> #include <QDebug>
#include <QModelIndex> #include <QModelIndex>
#include <QSqlRecord> #include <QSqlQuery>
#include <QUrl> #include <QUrl>
#include <QVariant>
#include "database.h" #include "database.h"
#include "feedListModel.h" #include "feedListModel.h"
#include "fetcher.h" #include "fetcher.h"
FeedListModel::FeedListModel(QObject *parent) FeedListModel::FeedListModel(QObject *parent)
: QSqlTableModel(parent) : QAbstractListModel(parent)
{ {
setTable(QStringLiteral("Feeds")); connect(&Database::instance(), &Database::feedAdded, this, [this]() {
setSort(0, Qt::AscendingOrder); beginInsertRows(QModelIndex(), rowCount(QModelIndex()) - 1, rowCount(QModelIndex()) - 1);
setEditStrategy(OnFieldChange); endInsertRows();
select(); });
connect(&Fetcher::instance(), &Fetcher::feedDetailsUpdated, this, [this](QString url, QString name, QString image) {
connect(&Fetcher::instance(), &Fetcher::updated, this, [this]() { select(); }); for (int i = rowCount(QModelIndex()) - 1; i >= 0; i--) {
if (m_feeds[i]->url() == url) {
m_feeds[i]->setName(name);
m_feeds[i]->setImage(image);
Q_EMIT dataChanged(createIndex(i, 0), createIndex(i, 0));
break;
}
}
});
} }
QHash<int, QByteArray> FeedListModel::roleNames() const QHash<int, QByteArray> FeedListModel::roleNames() const
{ {
QHash<int, QByteArray> roleNames; QHash<int, QByteArray> roleNames;
roleNames[Name] = "name"; roleNames[0] = "feed";
roleNames[Url] = "url";
roleNames[Image] = "image";
return roleNames; return roleNames;
} }
void FeedListModel::addFeed(QString url) int FeedListModel::rowCount(const QModelIndex &parent) const
{ {
Database::instance().addFeed(url); Q_UNUSED(parent)
QSqlQuery query;
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 FeedListModel::data(const QModelIndex &index, int role) const QVariant FeedListModel::data(const QModelIndex &index, int role) const
{ {
return QSqlTableModel::data(createIndex(index.row(), role), 0); if (role != 0)
return QVariant();
if (m_feeds[index.row()] == nullptr)
loadFeed(index.row());
return QVariant::fromValue(m_feeds[index.row()]);
}
void FeedListModel::loadFeed(int index) const
{
QSqlQuery query;
query.prepare(QStringLiteral("SELECT * FROM Feeds LIMIT 1 OFFSET :index;"));
query.bindValue(QStringLiteral(":index"), index);
Database::instance().execute(query);
if (!query.next())
qWarning() << "Failed to lod feed" << index;
Feed *feed = new Feed(query.value(QStringLiteral("url")).toString(), query.value(QStringLiteral("name")).toString(), query.value(QStringLiteral("image")).toString(), nullptr);
m_feeds[index] = feed;
} }
void FeedListModel::removeFeed(int index) void FeedListModel::removeFeed(int index)
{ {
Fetcher::instance().removeImage(data(createIndex(index, 0), Image).toString()); Feed *feed = m_feeds[index];
QSqlQuery query; beginRemoveRows(QModelIndex(), index, index);
query.prepare(QStringLiteral("DELETE FROM Authors WHERE feed=:feed;")); m_feeds[index] = nullptr;
query.bindValue(QStringLiteral(":feed"), data(createIndex(index, 0), 1).toString()); endRemoveRows();
Database::instance().execute(query); feed->remove();
delete feed;
query.prepare(QStringLiteral("DELETE FROM Entries WHERE feed=:feed;"));
query.bindValue(QStringLiteral(":feed"), data(createIndex(index, 0), 1).toString());
Database::instance().execute(query);
query.prepare(QStringLiteral("DELETE FROM Enclosures WHERE feed=:feed;"));
query.bindValue(QStringLiteral(":feed"), data(createIndex(index, 0), 1).toString());
Database::instance().execute(query);
// Workaround...
query.prepare(QStringLiteral("DELETE FROM Feeds WHERE url=:url;"));
query.bindValue(QStringLiteral(":url"), data(createIndex(index, 0), 1).toString());
Database::instance().execute(query);
select();
} }

View File

@ -20,27 +20,26 @@
#pragma once #pragma once
#include <QAbstractListModel>
#include <QHash>
#include <QSqlTableModel> #include <QSqlTableModel>
#include <QUrl> #include <QUrl>
#include <QVector>
//#include "feed.h" #include "feed.h"
#include "fetcher.h"
class FeedListModel : public QSqlTableModel class FeedListModel : public QAbstractListModel
{ {
Q_OBJECT Q_OBJECT
public: public:
enum DataRole {
Name = 0,
Url,
Image,
};
explicit FeedListModel(QObject *parent = nullptr); explicit FeedListModel(QObject *parent = nullptr);
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;
Q_INVOKABLE void addFeed(QString url);
Q_INVOKABLE void removeFeed(int index); Q_INVOKABLE void removeFeed(int index);
private:
void loadFeed(int index) const;
mutable QHash<int, Feed *> m_feeds;
}; };

View File

@ -38,27 +38,24 @@ Fetcher::Fetcher()
manager->enableStrictTransportSecurityStore(true); manager->enableStrictTransportSecurityStore(true);
} }
void Fetcher::fetch(QUrl url) void Fetcher::fetch(QString url)
{ {
qDebug() << "Starting to fetch" << url.toString(); qDebug() << "Starting to fetch" << url;
emit updated(); QNetworkRequest request((QUrl(url)));
QNetworkRequest request(url);
QNetworkReply *reply = manager->get(request); QNetworkReply *reply = manager->get(request);
connect(reply, &QNetworkReply::finished, this, [this, url, reply]() { connect(reply, &QNetworkReply::finished, this, [this, url, reply]() {
QByteArray data = reply->readAll(); QByteArray data = reply->readAll();
Syndication::DocumentSource *document = new Syndication::DocumentSource(data, url.toString()); Syndication::DocumentSource *document = new Syndication::DocumentSource(data, url);
Syndication::FeedPtr feed = Syndication::parserCollection()->parse(*document, QStringLiteral("Atom")); Syndication::FeedPtr feed = Syndication::parserCollection()->parse(*document, QStringLiteral("Atom"));
processFeed(feed, url); processFeed(feed, url);
emit updated();
delete reply; delete reply;
}); });
} }
void Fetcher::processFeed(Syndication::FeedPtr feed, QUrl url) void Fetcher::processFeed(Syndication::FeedPtr feed, QString url)
{ {
if (feed.isNull()) if (feed.isNull())
return; return;
@ -67,20 +64,25 @@ void Fetcher::processFeed(Syndication::FeedPtr feed, QUrl url)
query.prepare(QStringLiteral("UPDATE Feeds SET name=:name, image=:image WHERE url=:url;")); query.prepare(QStringLiteral("UPDATE Feeds SET name=:name, image=:image WHERE url=:url;"));
query.bindValue(QStringLiteral(":name"), feed->title()); query.bindValue(QStringLiteral(":name"), feed->title());
query.bindValue(QStringLiteral(":url"), url); query.bindValue(QStringLiteral(":url"), url);
if (feed->image()->url().startsWith(QStringLiteral("/"))) { QString image;
QString absolute = url.adjusted(QUrl::RemovePath).toString() + feed->image()->url(); if (feed->image()->url().startsWith(QStringLiteral("/")))
query.bindValue(QStringLiteral(":image"), absolute); image = QUrl(url).adjusted(QUrl::RemovePath).toString() + feed->image()->url();
} else else
query.bindValue(QStringLiteral(":image"), feed->image()->url()); image = feed->image()->url();
query.bindValue(QStringLiteral(":image"), image);
Database::instance().execute(query); Database::instance().execute(query);
qDebug() << "Updated feed title:" << feed->title(); qDebug() << "Updated feed title:" << feed->title();
Q_EMIT feedDetailsUpdated(url, feed->title(), image);
for (const auto &entry : feed->items()) { for (const auto &entry : feed->items()) {
processEntry(entry, url); processEntry(entry, url);
} }
Q_EMIT feedUpdated(url);
} }
void Fetcher::processEntry(Syndication::ItemPtr entry, QUrl url) void Fetcher::processEntry(Syndication::ItemPtr entry, QString url)
{ {
qDebug() << "Processing" << entry->title(); qDebug() << "Processing" << entry->title();
QSqlQuery query; QSqlQuery query;
@ -116,11 +118,11 @@ void Fetcher::processEntry(Syndication::ItemPtr entry, QUrl url)
} }
} }
void Fetcher::processAuthor(Syndication::PersonPtr author, Syndication::ItemPtr entry, QUrl url) void Fetcher::processAuthor(Syndication::PersonPtr author, Syndication::ItemPtr entry, QString url)
{ {
QSqlQuery query; QSqlQuery query;
query.prepare(QStringLiteral("INSERT INTO Authors VALUES(:feed, :id, :name, :uri, :email);")); query.prepare(QStringLiteral("INSERT INTO Authors VALUES(:feed, :id, :name, :uri, :email);"));
query.bindValue(QStringLiteral(":feed"), url.toString()); query.bindValue(QStringLiteral(":feed"), url);
query.bindValue(QStringLiteral(":id"), entry->id()); query.bindValue(QStringLiteral(":id"), entry->id());
query.bindValue(QStringLiteral(":name"), author->name()); query.bindValue(QStringLiteral(":name"), author->name());
query.bindValue(QStringLiteral(":uri"), author->uri()); query.bindValue(QStringLiteral(":uri"), author->uri());
@ -128,11 +130,11 @@ void Fetcher::processAuthor(Syndication::PersonPtr author, Syndication::ItemPtr
Database::instance().execute(query); Database::instance().execute(query);
} }
void Fetcher::processEnclosure(Syndication::EnclosurePtr enclosure, Syndication::ItemPtr entry, QUrl feedUrl) void Fetcher::processEnclosure(Syndication::EnclosurePtr enclosure, Syndication::ItemPtr entry, QString feedUrl)
{ {
QSqlQuery query; QSqlQuery query;
query.prepare(QStringLiteral("INSERT INTO Enclosures VALUES (:feed, :id, :duration, :size, :title, :type, :url);")); query.prepare(QStringLiteral("INSERT INTO Enclosures VALUES (:feed, :id, :duration, :size, :title, :type, :url);"));
query.bindValue(QStringLiteral(":feed"), feedUrl.toString()); query.bindValue(QStringLiteral(":feed"), feedUrl);
query.bindValue(QStringLiteral(":id"), entry->id()); query.bindValue(QStringLiteral(":id"), entry->id());
query.bindValue(QStringLiteral(":duration"), enclosure->duration()); query.bindValue(QStringLiteral(":duration"), enclosure->duration());
query.bindValue(QStringLiteral(":size"), enclosure->length()); query.bindValue(QStringLiteral(":size"), enclosure->length());
@ -165,7 +167,6 @@ void Fetcher::download(QString url)
file.write(data); file.write(data);
file.close(); file.close();
emit updated();
delete reply; delete reply;
}); });
} }

View File

@ -34,23 +34,23 @@ public:
static Fetcher _instance; static Fetcher _instance;
return _instance; return _instance;
} }
void fetch(QUrl); Q_INVOKABLE void fetch(QString url);
Q_INVOKABLE QString image(QString); Q_INVOKABLE QString image(QString);
void removeImage(QString); void removeImage(QString);
Q_INVOKABLE void download(QString url); Q_INVOKABLE void download(QString url);
private: private:
Fetcher(); Fetcher();
Fetcher(const Fetcher &);
QString filePath(QString); QString filePath(QString);
void processFeed(Syndication::FeedPtr feed, QUrl url); void processFeed(Syndication::FeedPtr feed, QString url);
void processEntry(Syndication::ItemPtr entry, QUrl url); void processEntry(Syndication::ItemPtr entry, QString url);
void processAuthor(Syndication::PersonPtr author, Syndication::ItemPtr entry, QUrl url); void processAuthor(Syndication::PersonPtr author, Syndication::ItemPtr entry, QString url);
void processEnclosure(Syndication::EnclosurePtr enclosure, Syndication::ItemPtr entry, QUrl feedUrl); void processEnclosure(Syndication::EnclosurePtr enclosure, Syndication::ItemPtr entry, QString feedUrl);
QNetworkAccessManager *manager; QNetworkAccessManager *manager;
Q_SIGNALS: Q_SIGNALS:
void updated(); void feedUpdated(QString url);
void feedDetailsUpdated(QString url, QString name, QString image);
}; };

View File

@ -61,6 +61,10 @@ int main(int argc, char *argv[])
engine->setObjectOwnership(&Fetcher::instance(), QQmlEngine::CppOwnership); engine->setObjectOwnership(&Fetcher::instance(), QQmlEngine::CppOwnership);
return &Fetcher::instance(); return &Fetcher::instance();
}); });
qmlRegisterSingletonType<Database>("org.kde.alligator", 1, 0, "Database", [](QQmlEngine *engine, QJSEngine *) -> QObject * {
engine->setObjectOwnership(&Database::instance(), QQmlEngine::CppOwnership);
return &Database::instance();
});
QQmlApplicationEngine engine; QQmlApplicationEngine engine;
engine.rootContext()->setContextObject(new KLocalizedContext(&engine)); engine.rootContext()->setContextObject(new KLocalizedContext(&engine));

View File

@ -29,13 +29,11 @@ import org.kde.alligator 1.0
Kirigami.ScrollablePage { Kirigami.ScrollablePage {
id: page id: page
property var name property var feed
property var url
property var image
title: name title: feed.title
property var all: page.url === "all" //property bool all: feed
contextualActions: [ contextualActions: [
Kirigami.Action { Kirigami.Action {
@ -48,11 +46,7 @@ Kirigami.ScrollablePage {
actions.main: Kirigami.Action { actions.main: Kirigami.Action {
iconName: "view-refresh" iconName: "view-refresh"
text: i18n("Refresh Feed") text: i18n("Refresh Feed")
onTriggered: entryListModel.fetch() onTriggered: Fetcher.fetch(page.feed.url)
}
Component.onCompleted: {
entryListModel.fetch();
} }
Kirigami.PlaceholderMessage { Kirigami.PlaceholderMessage {
@ -69,7 +63,7 @@ Kirigami.ScrollablePage {
visible: count !== 0 visible: count !== 0
model: EntryListModel { model: EntryListModel {
id: entryListModel id: entryListModel
feed: page.url feed: page.feed
} }
header: RowLayout { header: RowLayout {
@ -77,13 +71,12 @@ Kirigami.ScrollablePage {
height: root.height * 0.2 height: root.height * 0.2
visible: !all visible: !all
Kirigami.Icon { Kirigami.Icon {
source: Fetcher.image(page.image) source: Fetcher.image(page.feed.image)
width: height width: height
height: parent.height height: parent.height
Component.onCompleted: console.log("Height: " + page.height)
} }
Kirigami.Heading { Kirigami.Heading {
text: page.name text: page.feed.name
} }
} }
@ -93,14 +86,14 @@ Kirigami.ScrollablePage {
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Controls.Label { Controls.Label {
text: model.title text: model.entry.title
Layout.fillWidth: true Layout.fillWidth: true
elide: Text.ElideRight elide: Text.ElideRight
opacity: 1 opacity: 1
} }
Controls.Label { Controls.Label {
id: subtitleItem id: subtitleItem
text: model.updated.toLocaleString(Qt.locale(), Locale.ShortFormat) + (model.authors.length === 0 ? "" : " " + i18nc("by <author(s)>", "by") + " " + model.authors.join(", ")) text: model.entry.updated.toLocaleString(Qt.locale(), Locale.ShortFormat) + (model.entry.authors.length === 0 ? "" : " " + i18nc("by <author(s)>", "by") + " " + model.entry.authors[0].name)
Layout.fillWidth: true Layout.fillWidth: true
elide: Text.ElideRight elide: Text.ElideRight
font: Kirigami.Theme.smallFont font: Kirigami.Theme.smallFont
@ -110,8 +103,7 @@ Kirigami.ScrollablePage {
} }
onClicked: { onClicked: {
model.read = true; pageStack.push("qrc:/EntryPage.qml", {"entry": model.entry})
pageStack.push("qrc:/EntryPage.qml", {"data": model, "baseUrl": entryListModel.baseUrl(model.link)})
} }
} }
} }

View File

@ -28,15 +28,16 @@ import org.kde.alligator 1.0
Kirigami.ScrollablePage { Kirigami.ScrollablePage {
id: page id: page
property QtObject data
property alias baseUrl: label.baseUrl
title: data.title property QtObject entry
title: entry.title
ColumnLayout { ColumnLayout {
Controls.Label { Controls.Label {
id: label id: label
text: page.data.content baseUrl: page.entry.baseUrl
text: page.entry.content
textFormat: Text.RichText textFormat: Text.RichText
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
Layout.fillWidth: true Layout.fillWidth: true

View File

@ -58,7 +58,7 @@ Kirigami.ScrollablePage {
text: i18n("Add Feed") text: i18n("Add Feed")
enabled: urlField.text enabled: urlField.text
onClicked: { onClicked: {
feedListModel.addFeed(urlField.text) Database.addFeed(urlField.text)
addSheet.close() addSheet.close()
} }
} }
@ -82,6 +82,7 @@ Kirigami.ScrollablePage {
id: feedListModel id: feedListModel
} }
/*
header: header:
Kirigami.AbstractListItem { Kirigami.AbstractListItem {
Controls.Label { Controls.Label {
@ -95,6 +96,7 @@ Kirigami.ScrollablePage {
pageStack.push("qrc:/EntryListPage.qml") pageStack.push("qrc:/EntryListPage.qml")
} }
} }
*/
delegate: Kirigami.SwipeListItem { delegate: Kirigami.SwipeListItem {
height: Kirigami.Units.gridUnit*2 height: Kirigami.Units.gridUnit*2
@ -102,13 +104,13 @@ Kirigami.ScrollablePage {
Item { Item {
Kirigami.Icon { Kirigami.Icon {
id: icon id: icon
source: Fetcher.image(model.image) source: Fetcher.image(model.feed.image)
width: height width: height
height: parent.height height: parent.height
} }
Controls.Label { Controls.Label {
text: model.name text: model.feed.name
height: parent.height height: parent.height
anchors.left: icon.right anchors.left: icon.right
leftPadding: 0.5*Kirigami.Units.gridUnit leftPadding: 0.5*Kirigami.Units.gridUnit
@ -119,7 +121,7 @@ Kirigami.ScrollablePage {
Kirigami.Action { Kirigami.Action {
icon.name: "delete" icon.name: "delete"
onTriggered: { onTriggered: {
if(pageStack.depth > 1 && model.url === lastFeed) if(pageStack.depth > 1 && model.feed.url === lastFeed)
pageStack.pop() pageStack.pop()
feedListModel.removeFeed(index) feedListModel.removeFeed(index)
} }
@ -128,8 +130,8 @@ Kirigami.ScrollablePage {
] ]
onClicked: { onClicked: {
lastFeed = model.url lastFeed = model.feed.url
pageStack.push("qrc:/EntryListPage.qml", {"name": name, "url": url, "image": image}) pageStack.push("qrc:/EntryListPage.qml", {"feed": model.feed})
} }
} }
} }