port models to QSqlTableModel

This commit is contained in:
Tobias Fella 2020-04-18 21:07:49 +02:00
parent ff7c03a32c
commit eb8d24e28a
15 changed files with 109 additions and 411 deletions

View File

@ -3,8 +3,6 @@ set(alligator_SRCS
feedListModel.cpp
entryListModel.cpp
fetcher.cpp
feed.cpp
entry.cpp
database.cpp
resources.qrc
)

View File

@ -46,12 +46,12 @@ Database::Database()
}
bool Database::migrate() {
qDebug() << "Migrating database";
if(version() < 1) TRUE_OR_RETURN(migrateTo1());
return true;
}
bool Database::migrateTo1() {
qDebug() << "Migrating database to version 1";
QSqlQuery query(QSqlDatabase::database());
TRUE_OR_RETURN(execute(QStringLiteral("CREATE TABLE IF NOT EXISTS Feeds (name TEXT, url TEXT, image TEXT);")));
TRUE_OR_RETURN(execute(QStringLiteral("CREATE TABLE IF NOT EXISTS Entries (feed TEXT, id TEXT UNIQUE, title TEXT, content TEXT, created INTEGER, updated INTEGER);")));

View File

@ -1,74 +0,0 @@
/**
* 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"
Entry::Entry(const QString title, const QString content, const int updated, const bool bookmark, const bool read)
: m_title(title)
, m_content(content)
, m_updated(updated)
, m_bookmark(bookmark)
, m_read(read)
{
}
Entry::Entry(const Entry &other)
: m_title(other.title())
, m_content(other.content())
, m_updated(other.updated())
, m_bookmark(other.isBookmark())
, m_read(other.isRead())
{
}
bool Entry::isRead() const
{
return m_read;
}
bool Entry::isBookmark() const
{
return m_bookmark;
}
QString Entry::title() const
{
return m_title;
}
QString Entry::content() const
{
return m_content;
}
int Entry::updated() const
{
return m_updated;
}
void Entry::setRead(bool read)
{
m_read = read;
}
void Entry::setBookmark(bool bookmark)
{
m_bookmark = bookmark;
}

View File

@ -1,46 +0,0 @@
/**
* 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/>.
*/
#pragma once
#include <QString>
class Entry
{
public:
Entry(const Entry &);
Entry(const QString title, const QString content, const int updated, const bool bookmark, const bool read);
QString title() const;
QString content() const;
int updated() const;
bool isBookmark() const;
bool isRead() const;
void setRead(bool read);
void setBookmark(bool bookmark);
private:
QString m_title;
QString m_content;
int m_updated;
bool m_bookmark;
bool m_read;
};

View File

@ -26,87 +26,49 @@
#include "database.h"
EntryListModel::EntryListModel(QObject *parent)
: QAbstractListModel(parent)
: QSqlTableModel(parent)
{
setTable("entries");
setSort(Updated, Qt::DescendingOrder);
setEditStrategy(OnFieldChange);
select();
}
QVariant EntryListModel::data(const QModelIndex &index, int role) const
{
if (role == Title)
return m_entries[index.row()].title();
if (role == Content)
return m_entries[index.row()].content();
if (role == Updated) {
if(role == Updated || role == Created) {
QDateTime updated;
updated.setSecsSinceEpoch(m_entries[index.row()].updated());
updated.setSecsSinceEpoch(QSqlQueryModel::data(createIndex(index.row(), role), 0).toInt());
return updated;
}
if (role == Bookmark)
return m_entries[index.row()].isBookmark();
if (role == Read)
return m_entries[index.row()].isRead();
return QSqlQueryModel::data(createIndex(index.row(), role), 0);
}
return QStringLiteral("DEADBEEF");
}
int EntryListModel::rowCount(const QModelIndex &index) const
{
return m_entries.size();
}
QHash<int, QByteArray> EntryListModel::roleNames() const
{
QHash<int, QByteArray> roleNames;
roleNames[Feed] = "feed";
roleNames[Id] = "id";
roleNames[Title] = "title";
roleNames[Content] = "content";
roleNames[Created] = "created";
roleNames[Updated] = "updated";
roleNames[Bookmark] = "bookmark";
roleNames[Read] = "read";
return roleNames;
}
bool EntryListModel::setData(const QModelIndex &index, const QVariant &value, int role)
void EntryListModel::setFeed(QString url)
{
if (role == Bookmark) {
m_entries[index.row()].setBookmark(value.toBool());
} else if (role == Read) {
m_entries[index.row()].setRead(value.toBool());
}
emit dataChanged(index, index, QVector<int>({role}));
return true;
m_feed = url;
setFilter(QStringLiteral("feed ='%1'").arg(url));
select();
}
void EntryListModel::fetch()
{
connect(&Fetcher::instance(), &Fetcher::finished, this, &EntryListModel::update);
if(m_feed.url().compare("all") != 0)
Fetcher::instance().fetch(m_feed.url());
else
update();
}
void EntryListModel::update() {
beginResetModel();
QSqlQuery query;
if(m_feed.url().compare("all") == 0) {
query.prepare(QStringLiteral("SELECT id, title, content, updated FROM Entries ORDER BY updated DESC;"));
}
else {
query.prepare(QStringLiteral("SELECT id, title, content, updated FROM Entries WHERE feed=:feed ORDER BY updated DESC;"));
query.bindValue(QStringLiteral(":feed"), m_feed.url());
}
Database::instance().execute(query);
while (query.next()) {
m_entries.append(Entry(query.value(1).toString(), query.value(2).toString(), query.value(3).toInt(), false, false));
}
endResetModel();
}
Feed EntryListModel::feed()
QString EntryListModel::feed() const
{
return m_feed;
}
void EntryListModel::setFeed(Feed feed)
void EntryListModel::fetch()
{
m_feed = feed;
emit feedChanged(feed);
Fetcher::instance().fetch(m_feed);
}

View File

@ -20,41 +20,32 @@
#pragma once
#include <QAbstractListModel>
#include <QSqlTableModel>
#include <QObject>
#include <QString>
#include "entry.h"
#include "feed.h"
class EntryListModel : public QAbstractListModel
class EntryListModel : public QSqlTableModel
{
Q_OBJECT
Q_PROPERTY(Feed feed READ feed WRITE setFeed NOTIFY feedChanged)
Q_PROPERTY(QString feed READ feed WRITE setFeed)
public:
enum DataRole {
Title = Qt::UserRole + 1,
Feed = 0,
Id,
Title,
Content,
Created,
Updated,
Bookmark,
Read,
};
explicit EntryListModel(QObject *parent = nullptr);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &index) const override;
QHash<int, QByteArray> roleNames() const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
Feed feed();
void setFeed(Feed feed);
Q_INVOKABLE void fetch();
Q_SIGNALS:
void feedChanged(Feed);
QString feed() const;
void setFeed(QString feed);
private:
QVector<Entry> m_entries;
Feed m_feed;
void update();
QString m_feed;
};

View File

@ -1,79 +0,0 @@
/**
* 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 "feed.h"
Feed::Feed()
: m_url(QStringLiteral(""))
, m_name(QStringLiteral(""))
, m_image(QStringLiteral(""))
{
}
Feed::Feed(const QString url)
: m_url(url)
, m_name(url)
, m_image(QStringLiteral(""))
{
}
Feed::Feed(const QString url, const QString name, const QString image)
: m_url(url)
, m_name(name)
, m_image(image)
{
}
Feed::Feed(const Feed &other)
: m_url(other.url())
, m_name(other.name())
, m_image(other.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;
}
void Feed::setImage(QString image)
{
m_image = image;
}

View File

@ -1,53 +0,0 @@
/**
* 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/>.
*/
#pragma once
#include <QObject>
#include <QString>
class Feed
{
Q_GADGET
Q_PROPERTY(QString url READ url)
Q_PROPERTY(QString name READ name WRITE setName)
Q_PROPERTY(QString image READ image WRITE setImage)
public:
Feed();
Feed(const QString url);
Feed(const QString url, const QString name, const QString image);
Feed(const Feed &other);
~Feed();
QString name() const;
QString url() const;
QString image() const;
private:
QString m_url;
QString m_name;
QString m_image;
void setName(QString);
void setImage(QString);
};
Q_DECLARE_METATYPE(Feed);

View File

@ -19,86 +19,77 @@
*/
#include <QUrl>
#include <QSqlRecord>
#include <QDebug>
#include <QModelIndex>
#include <QSqlError>
#include "feedListModel.h"
#include "fetcher.h"
#include "database.h"
FeedListModel::FeedListModel(QObject *parent)
: QAbstractListModel(parent)
: QSqlTableModel(parent)
{
QSqlQuery query;
query.prepare(QStringLiteral("SELECT name, url, image FROM Feeds"));
Database::instance().execute(query);
beginInsertRows(QModelIndex(), 0, query.size());
while (query.next()) {
feeds += Feed(query.value(1).toString(), query.value(0).toString(), query.value(2).toString());
}
endInsertRows();
setTable("Feeds");
setSort(0, Qt::AscendingOrder);
setEditStrategy(OnFieldChange);
select();
}
QHash<int, QByteArray> FeedListModel::roleNames() const
{
QHash<int, QByteArray> roleNames;
roleNames[FeedRole] = "feed";
roleNames[Name] = "name";
roleNames[Url] = "url";
roleNames[Image] = "image";
return roleNames;
}
QVariant FeedListModel::data(const QModelIndex &index, int role) const
{
if (role == FeedRole) {
return QVariant::fromValue(feeds[index.row()]);
}
return QStringLiteral("DEADBEEF");
}
int FeedListModel::rowCount(const QModelIndex &index) const
{
return feeds.size();
}
void FeedListModel::addFeed(QString url)
{
qDebug() << "Adding feed";
if(feedExists(url)) {
qDebug() << "Feed already exists";
return;
}
qDebug() << "Feed does not yet exist";
QSqlRecord rec = record();
rec.setValue(0, url);
rec.setValue(1, url);
rec.setValue(2, "");
insertRecord(-1, rec);
connect(&Fetcher::instance(), &Fetcher::updated, this, [this]() {
select();
disconnect(&Fetcher::instance(), &Fetcher::updated, nullptr, nullptr);
});
Fetcher::instance().fetch(QUrl(url));
}
QVariant FeedListModel::data(const QModelIndex &index, int role) const
{
return QSqlTableModel::data(createIndex(index.row(), role), 0);
}
bool FeedListModel::feedExists(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();
if(query.value(0).toInt() != 0) return;
connect(&Fetcher::instance(), &Fetcher::finished, this, [this, url]() {
QSqlQuery query;
query.prepare(QStringLiteral("SELECT name, image FROM Feeds WHERE url=:url;"));
query.bindValue(QStringLiteral(":url"), url);
Database::instance().execute(query);
query.next();
for(int i = 0; i < feeds.length(); i++) {
if(feeds[i].url() == url) {
feeds.removeAt(i);
feeds.insert(i, Feed(url, query.value(0).toString(), query.value(1).toString()));
emit dataChanged(index(i), index(i));
break;
}
}
});
Fetcher::instance().fetch(QUrl(url));
beginInsertRows(QModelIndex(), feeds.size(), feeds.size());
feeds.append(Feed(url));
endInsertRows();
query.prepare(QStringLiteral("INSERT INTO Feeds VALUES (:url, :name, '');"));
query.bindValue(QStringLiteral(":url"), url);
query.bindValue(QStringLiteral(":name"), url);
Database::instance().execute(query);
return query.value(0).toInt() != 0;
}
void FeedListModel::removeFeed(int index)
{
Feed toRemove = feeds[index];
//Workaround...
QSqlQuery query;
query.prepare(QStringLiteral("DELETE FROM Feeds WHERE name=:name AND url=url;"));
query.bindValue(QStringLiteral(":url"), toRemove.url());
query.bindValue(QStringLiteral(":name"), toRemove.name());
query.prepare("DELETE FROM Feeds WHERE url=:url");
query.bindValue(":url", data(createIndex(index, 0), 1).toString());
Database::instance().execute(query);
beginRemoveRows(QModelIndex(), index, index);
feeds.remove(index);
endRemoveRows();
select();
}

View File

@ -20,29 +20,30 @@
#pragma once
#include <QAbstractListModel>
#include <QSqlTableModel>
#include <QUrl>
#include <QVector>
#include "feed.h"
//#include "feed.h"
#include "fetcher.h"
class FeedListModel : public QAbstractListModel
class FeedListModel : public QSqlTableModel
{
Q_OBJECT
public:
enum DataRole {
FeedRole = Qt::UserRole + 1,
Name = 0,
Url,
Image,
};
explicit FeedListModel(QObject *parent = nullptr);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &index) const override;
QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE void addFeed(QString url);
Q_INVOKABLE void removeFeed(int index);
private:
QVector<Feed> feeds;
bool feedExists(QString url);
};

View File

@ -31,6 +31,7 @@ Fetcher::Fetcher() {
void Fetcher::fetch(QUrl url)
{
qDebug() << "Starting to fetch" << url.toString();
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
manager->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
manager->setStrictTransportSecurityEnabled(true);
@ -47,7 +48,15 @@ void Fetcher::fetch(QUrl url)
QSqlQuery query;
query.prepare(QStringLiteral("UPDATE Feeds SET name=:name, image=:image WHERE url=:url;"));
query.bindValue(QStringLiteral(":name"), feed->title());
query.bindValue(QStringLiteral(":url"), url.toString());
query.bindValue(QStringLiteral(":image"), feed->image()->url());
Database::instance().execute(query);
qDebug() << "Updated feed title:" << feed->title();
for (const auto &entry : feed->items()) {
qDebug() << "Processing" << entry->title();
query.prepare(QStringLiteral("SELECT COUNT (id) FROM Entries WHERE id=:id;"));
query.bindValue(QStringLiteral(":id"), entry->id());
Database::instance().execute(query);
@ -72,13 +81,9 @@ void Fetcher::fetch(QUrl url)
query.bindValue(QStringLiteral(":email"), author->email());
Database::instance().execute(query);
}
query.prepare(QStringLiteral("UPDATE Feeds SET name=:name, image=:image WHERE url=:url;"));
query.bindValue(QStringLiteral(":name"), feed->title());
query.bindValue(QStringLiteral(":url"), url.toString());
query.bindValue(QStringLiteral(":image"), feed->image()->url());
Database::instance().execute(query);
}
emit updated();
delete reply;
emit finished();
});
}

View File

@ -39,5 +39,5 @@ private:
Fetcher(const Fetcher &);
Q_SIGNALS:
void finished();
void updated();
};

View File

@ -43,7 +43,6 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
qmlRegisterType<FeedListModel>("org.kde.alligator", 1, 0, "FeedListModel");
qmlRegisterType<EntryListModel>("org.kde.alligator", 1, 0, "EntryListModel");
qRegisterMetaType<Feed>();
QQmlApplicationEngine engine;

View File

@ -28,17 +28,19 @@ import org.kde.alligator 1.0
Kirigami.ScrollablePage {
id: page
property var feed
property var data
property var all: page.data.url === "all"
contextualActions: [
Kirigami.Action {
text: i18n("Details")
visible: feed.url != "all"
visible: !all
onTriggered: ;//pageStack.push("qrc:/qml/FeedDetailsPage.qml", {"modelData": atomModel})
}
]
Component.onCompleted: {
entryListModel.fetch();
}
@ -46,27 +48,28 @@ Kirigami.ScrollablePage {
ListView {
model: EntryListModel {
id: entryListModel
feed: page.feed
feed: page.data.url
}
header: RowLayout {
width: parent.width
height: page.height * 0.2
visible: !all
Image {
source: feed.image
source: page.data.image
fillMode: Image.PreserveAspectFit
sourceSize.width: 0
sourceSize.height: parent.height
}
Kirigami.Heading {
text: feed.name
text: page.data.name
}
}
delegate: Kirigami.SwipeListItem {
Controls.Label {
width: parent.width
text: model.title + " - " + model.updated
text: model.title + " - " + model.updated.toLocaleString(Qt.locale(), Locale.ShortFormat)
textFormat: Text.RichText
color: model.read ? Kirigami.Theme.disabledTextColor : Kirigami.Theme.textColor
}

View File

@ -86,13 +86,13 @@ Kirigami.ScrollablePage {
Item {
Kirigami.Icon {
id: icon
source: model.feed.image
source: model.image
width: height
height: parent.height
}
Controls.Label {
text: model.feed.name
text: model.name
height: parent.height
anchors.left: icon.right
leftPadding: 0.5*Kirigami.Units.gridUnit
@ -107,7 +107,7 @@ Kirigami.ScrollablePage {
]
onClicked: pageStack.push("qrc:/EntryListPage.qml", {"feed": feed})
onClicked: pageStack.push("qrc:/EntryListPage.qml", {"data": model})
}
}
}