Refactor models
This commit is contained in:
parent
8a05a47e40
commit
8269cb960f
@ -4,6 +4,9 @@ set(alligator_SRCS
|
||||
entryListModel.cpp
|
||||
fetcher.cpp
|
||||
database.cpp
|
||||
entry.cpp
|
||||
feed.cpp
|
||||
author.cpp
|
||||
resources.qrc
|
||||
)
|
||||
|
||||
|
48
src/author.cpp
Normal file
48
src/author.cpp
Normal 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
48
src/author.h
Normal 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
|
@ -151,5 +151,7 @@ void Database::addFeed(QString url)
|
||||
query.bindValue(QStringLiteral(":image"), QLatin1String(""));
|
||||
execute(query);
|
||||
|
||||
Fetcher::instance().fetch(QUrl(url));
|
||||
Q_EMIT feedAdded(url);
|
||||
|
||||
Fetcher::instance().fetch(url);
|
||||
}
|
||||
|
@ -22,8 +22,10 @@
|
||||
|
||||
#include <QSqlQuery>
|
||||
|
||||
class Database
|
||||
class Database : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static Database &instance()
|
||||
{
|
||||
@ -32,7 +34,10 @@ public:
|
||||
}
|
||||
bool execute(QSqlQuery &query);
|
||||
bool execute(QString query);
|
||||
void addFeed(QString url);
|
||||
Q_INVOKABLE void addFeed(QString url);
|
||||
|
||||
Q_SIGNALS:
|
||||
void feedAdded(QString url);
|
||||
|
||||
private:
|
||||
Database();
|
||||
|
72
src/entry.cpp
Normal file
72
src/entry.cpp
Normal 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
67
src/entry.h
Normal 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
|
@ -18,8 +18,8 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QSqlQuery>
|
||||
#include <QAbstractListModel>
|
||||
#include <QVariant>
|
||||
#include <QVector>
|
||||
|
||||
#include "database.h"
|
||||
@ -27,84 +27,90 @@
|
||||
#include "fetcher.h"
|
||||
|
||||
EntryListModel::EntryListModel(QObject *parent)
|
||||
: QSqlTableModel(parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
setTable(QStringLiteral("entries"));
|
||||
setSort(Updated, Qt::DescendingOrder);
|
||||
setEditStrategy(OnFieldChange);
|
||||
select();
|
||||
|
||||
connect(&Fetcher::instance(), &Fetcher::updated, this, [this]() { select(); });
|
||||
connect(&Fetcher::instance(), &Fetcher::feedUpdated, this, [this](QString url) {
|
||||
if (m_feed->url() == url) {
|
||||
beginResetModel();
|
||||
for (auto &entry : m_entries) {
|
||||
delete entry;
|
||||
}
|
||||
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
|
||||
{
|
||||
if (role == Enclosure) {
|
||||
return enclosure(data(index, Id).toString());
|
||||
}
|
||||
if (role == Authors) {
|
||||
QSqlQuery query;
|
||||
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("");
|
||||
if (role != 0)
|
||||
return QVariant();
|
||||
if (m_entries[index.row()] == nullptr)
|
||||
loadEntry(index.row());
|
||||
return QVariant::fromValue(m_entries[index.row()]);
|
||||
}
|
||||
|
||||
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[Link] = "link";
|
||||
roleNames[Authors] = "authors";
|
||||
roleNames[Enclosure] = "enclosure";
|
||||
roleNames[0] = "entry";
|
||||
return roleNames;
|
||||
}
|
||||
|
||||
void EntryListModel::setFeed(QString url)
|
||||
int EntryListModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
m_feed = url;
|
||||
setFilter(QStringLiteral("feed ='%1'").arg(url));
|
||||
select();
|
||||
emit feedChanged(url);
|
||||
Q_UNUSED(parent)
|
||||
QSqlQuery query;
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void EntryListModel::fetch()
|
||||
void EntryListModel::setFeed(Feed *feed)
|
||||
{
|
||||
Fetcher::instance().fetch(QUrl(m_feed));
|
||||
}
|
||||
|
||||
QString EntryListModel::baseUrl(QString url)
|
||||
{
|
||||
return QUrl(url).adjusted(QUrl::RemovePath).toString();
|
||||
m_feed = feed;
|
||||
emit feedChanged(feed);
|
||||
}
|
||||
|
@ -20,41 +20,35 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
#include <QSqlTableModel>
|
||||
#include <QString>
|
||||
|
||||
class EntryListModel : public QSqlTableModel
|
||||
#include "entry.h"
|
||||
|
||||
class EntryListModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString feed READ feed WRITE setFeed NOTIFY feedChanged)
|
||||
|
||||
Q_PROPERTY(Feed *feed READ feed WRITE setFeed NOTIFY feedChanged)
|
||||
|
||||
public:
|
||||
enum DataRole {
|
||||
Feed = 0,
|
||||
Id,
|
||||
Title,
|
||||
Content,
|
||||
Created,
|
||||
Updated,
|
||||
Link,
|
||||
Authors,
|
||||
Enclosure,
|
||||
};
|
||||
explicit EntryListModel(QObject *parent = nullptr);
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
|
||||
Q_INVOKABLE void fetch();
|
||||
Q_INVOKABLE QString baseUrl(QString url);
|
||||
Feed *feed() const;
|
||||
|
||||
QString feed() const;
|
||||
void setFeed(QString feed);
|
||||
void setFeed(Feed *feed);
|
||||
|
||||
Q_SIGNALS:
|
||||
void feedChanged(QString feed);
|
||||
void feedChanged(Feed *feed);
|
||||
|
||||
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
84
src/feed.cpp
Normal 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
58
src/feed.h
Normal 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
|
@ -20,62 +20,78 @@
|
||||
|
||||
#include <QDebug>
|
||||
#include <QModelIndex>
|
||||
#include <QSqlRecord>
|
||||
#include <QSqlQuery>
|
||||
#include <QUrl>
|
||||
#include <QVariant>
|
||||
|
||||
#include "database.h"
|
||||
#include "feedListModel.h"
|
||||
#include "fetcher.h"
|
||||
|
||||
FeedListModel::FeedListModel(QObject *parent)
|
||||
: QSqlTableModel(parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
setTable(QStringLiteral("Feeds"));
|
||||
setSort(0, Qt::AscendingOrder);
|
||||
setEditStrategy(OnFieldChange);
|
||||
select();
|
||||
|
||||
connect(&Fetcher::instance(), &Fetcher::updated, this, [this]() { select(); });
|
||||
connect(&Database::instance(), &Database::feedAdded, this, [this]() {
|
||||
beginInsertRows(QModelIndex(), rowCount(QModelIndex()) - 1, rowCount(QModelIndex()) - 1);
|
||||
endInsertRows();
|
||||
});
|
||||
connect(&Fetcher::instance(), &Fetcher::feedDetailsUpdated, this, [this](QString url, QString name, QString image) {
|
||||
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> roleNames;
|
||||
roleNames[Name] = "name";
|
||||
roleNames[Url] = "url";
|
||||
roleNames[Image] = "image";
|
||||
roleNames[0] = "feed";
|
||||
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
|
||||
{
|
||||
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)
|
||||
{
|
||||
Fetcher::instance().removeImage(data(createIndex(index, 0), Image).toString());
|
||||
QSqlQuery query;
|
||||
query.prepare(QStringLiteral("DELETE FROM Authors WHERE feed=:feed;"));
|
||||
query.bindValue(QStringLiteral(":feed"), data(createIndex(index, 0), 1).toString());
|
||||
Database::instance().execute(query);
|
||||
|
||||
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();
|
||||
Feed *feed = m_feeds[index];
|
||||
beginRemoveRows(QModelIndex(), index, index);
|
||||
m_feeds[index] = nullptr;
|
||||
endRemoveRows();
|
||||
feed->remove();
|
||||
delete feed;
|
||||
}
|
||||
|
@ -20,27 +20,26 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QHash>
|
||||
#include <QSqlTableModel>
|
||||
#include <QUrl>
|
||||
#include <QVector>
|
||||
|
||||
//#include "feed.h"
|
||||
#include "fetcher.h"
|
||||
#include "feed.h"
|
||||
|
||||
class FeedListModel : public QSqlTableModel
|
||||
class FeedListModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum DataRole {
|
||||
Name = 0,
|
||||
Url,
|
||||
Image,
|
||||
};
|
||||
explicit FeedListModel(QObject *parent = nullptr);
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
Q_INVOKABLE void addFeed(QString url);
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
Q_INVOKABLE void removeFeed(int index);
|
||||
|
||||
private:
|
||||
void loadFeed(int index) const;
|
||||
|
||||
mutable QHash<int, Feed *> m_feeds;
|
||||
};
|
||||
|
@ -38,27 +38,24 @@ Fetcher::Fetcher()
|
||||
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(url);
|
||||
QNetworkRequest request((QUrl(url)));
|
||||
QNetworkReply *reply = manager->get(request);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, url, reply]() {
|
||||
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"));
|
||||
|
||||
processFeed(feed, url);
|
||||
|
||||
emit updated();
|
||||
delete reply;
|
||||
});
|
||||
}
|
||||
|
||||
void Fetcher::processFeed(Syndication::FeedPtr feed, QUrl url)
|
||||
void Fetcher::processFeed(Syndication::FeedPtr feed, QString url)
|
||||
{
|
||||
if (feed.isNull())
|
||||
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.bindValue(QStringLiteral(":name"), feed->title());
|
||||
query.bindValue(QStringLiteral(":url"), url);
|
||||
if (feed->image()->url().startsWith(QStringLiteral("/"))) {
|
||||
QString absolute = url.adjusted(QUrl::RemovePath).toString() + feed->image()->url();
|
||||
query.bindValue(QStringLiteral(":image"), absolute);
|
||||
} else
|
||||
query.bindValue(QStringLiteral(":image"), feed->image()->url());
|
||||
QString image;
|
||||
if (feed->image()->url().startsWith(QStringLiteral("/")))
|
||||
image = QUrl(url).adjusted(QUrl::RemovePath).toString() + feed->image()->url();
|
||||
else
|
||||
image = feed->image()->url();
|
||||
query.bindValue(QStringLiteral(":image"), image);
|
||||
Database::instance().execute(query);
|
||||
qDebug() << "Updated feed title:" << feed->title();
|
||||
|
||||
Q_EMIT feedDetailsUpdated(url, feed->title(), image);
|
||||
|
||||
for (const auto &entry : feed->items()) {
|
||||
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();
|
||||
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;
|
||||
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(":name"), author->name());
|
||||
query.bindValue(QStringLiteral(":uri"), author->uri());
|
||||
@ -128,11 +130,11 @@ void Fetcher::processAuthor(Syndication::PersonPtr author, Syndication::ItemPtr
|
||||
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;
|
||||
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(":duration"), enclosure->duration());
|
||||
query.bindValue(QStringLiteral(":size"), enclosure->length());
|
||||
@ -165,7 +167,6 @@ void Fetcher::download(QString url)
|
||||
file.write(data);
|
||||
file.close();
|
||||
|
||||
emit updated();
|
||||
delete reply;
|
||||
});
|
||||
}
|
||||
|
@ -34,23 +34,23 @@ public:
|
||||
static Fetcher _instance;
|
||||
return _instance;
|
||||
}
|
||||
void fetch(QUrl);
|
||||
Q_INVOKABLE void fetch(QString url);
|
||||
Q_INVOKABLE QString image(QString);
|
||||
void removeImage(QString);
|
||||
Q_INVOKABLE void download(QString url);
|
||||
|
||||
private:
|
||||
Fetcher();
|
||||
Fetcher(const Fetcher &);
|
||||
|
||||
QString filePath(QString);
|
||||
void processFeed(Syndication::FeedPtr feed, QUrl url);
|
||||
void processEntry(Syndication::ItemPtr entry, QUrl url);
|
||||
void processAuthor(Syndication::PersonPtr author, Syndication::ItemPtr entry, QUrl url);
|
||||
void processEnclosure(Syndication::EnclosurePtr enclosure, Syndication::ItemPtr entry, QUrl feedUrl);
|
||||
void processFeed(Syndication::FeedPtr feed, QString url);
|
||||
void processEntry(Syndication::ItemPtr entry, QString url);
|
||||
void processAuthor(Syndication::PersonPtr author, Syndication::ItemPtr entry, QString url);
|
||||
void processEnclosure(Syndication::EnclosurePtr enclosure, Syndication::ItemPtr entry, QString feedUrl);
|
||||
|
||||
QNetworkAccessManager *manager;
|
||||
|
||||
Q_SIGNALS:
|
||||
void updated();
|
||||
void feedUpdated(QString url);
|
||||
void feedDetailsUpdated(QString url, QString name, QString image);
|
||||
};
|
||||
|
@ -61,6 +61,10 @@ int main(int argc, char *argv[])
|
||||
engine->setObjectOwnership(&Fetcher::instance(), QQmlEngine::CppOwnership);
|
||||
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;
|
||||
engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
|
||||
|
@ -29,13 +29,11 @@ import org.kde.alligator 1.0
|
||||
Kirigami.ScrollablePage {
|
||||
id: page
|
||||
|
||||
property var name
|
||||
property var url
|
||||
property var image
|
||||
property var feed
|
||||
|
||||
title: name
|
||||
title: feed.title
|
||||
|
||||
property var all: page.url === "all"
|
||||
//property bool all: feed
|
||||
|
||||
contextualActions: [
|
||||
Kirigami.Action {
|
||||
@ -48,11 +46,7 @@ Kirigami.ScrollablePage {
|
||||
actions.main: Kirigami.Action {
|
||||
iconName: "view-refresh"
|
||||
text: i18n("Refresh Feed")
|
||||
onTriggered: entryListModel.fetch()
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
entryListModel.fetch();
|
||||
onTriggered: Fetcher.fetch(page.feed.url)
|
||||
}
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
@ -69,7 +63,7 @@ Kirigami.ScrollablePage {
|
||||
visible: count !== 0
|
||||
model: EntryListModel {
|
||||
id: entryListModel
|
||||
feed: page.url
|
||||
feed: page.feed
|
||||
}
|
||||
|
||||
header: RowLayout {
|
||||
@ -77,13 +71,12 @@ Kirigami.ScrollablePage {
|
||||
height: root.height * 0.2
|
||||
visible: !all
|
||||
Kirigami.Icon {
|
||||
source: Fetcher.image(page.image)
|
||||
source: Fetcher.image(page.feed.image)
|
||||
width: height
|
||||
height: parent.height
|
||||
Component.onCompleted: console.log("Height: " + page.height)
|
||||
}
|
||||
Kirigami.Heading {
|
||||
text: page.name
|
||||
text: page.feed.name
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,14 +86,14 @@ Kirigami.ScrollablePage {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Controls.Label {
|
||||
text: model.title
|
||||
text: model.entry.title
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
opacity: 1
|
||||
}
|
||||
Controls.Label {
|
||||
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
|
||||
elide: Text.ElideRight
|
||||
font: Kirigami.Theme.smallFont
|
||||
@ -110,8 +103,7 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
model.read = true;
|
||||
pageStack.push("qrc:/EntryPage.qml", {"data": model, "baseUrl": entryListModel.baseUrl(model.link)})
|
||||
pageStack.push("qrc:/EntryPage.qml", {"entry": model.entry})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,15 +28,16 @@ import org.kde.alligator 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
id: page
|
||||
property QtObject data
|
||||
property alias baseUrl: label.baseUrl
|
||||
|
||||
title: data.title
|
||||
property QtObject entry
|
||||
|
||||
title: entry.title
|
||||
|
||||
ColumnLayout {
|
||||
Controls.Label {
|
||||
id: label
|
||||
text: page.data.content
|
||||
baseUrl: page.entry.baseUrl
|
||||
text: page.entry.content
|
||||
textFormat: Text.RichText
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
|
@ -58,7 +58,7 @@ Kirigami.ScrollablePage {
|
||||
text: i18n("Add Feed")
|
||||
enabled: urlField.text
|
||||
onClicked: {
|
||||
feedListModel.addFeed(urlField.text)
|
||||
Database.addFeed(urlField.text)
|
||||
addSheet.close()
|
||||
}
|
||||
}
|
||||
@ -82,6 +82,7 @@ Kirigami.ScrollablePage {
|
||||
id: feedListModel
|
||||
}
|
||||
|
||||
/*
|
||||
header:
|
||||
Kirigami.AbstractListItem {
|
||||
Controls.Label {
|
||||
@ -95,6 +96,7 @@ Kirigami.ScrollablePage {
|
||||
pageStack.push("qrc:/EntryListPage.qml")
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
delegate: Kirigami.SwipeListItem {
|
||||
height: Kirigami.Units.gridUnit*2
|
||||
@ -102,13 +104,13 @@ Kirigami.ScrollablePage {
|
||||
Item {
|
||||
Kirigami.Icon {
|
||||
id: icon
|
||||
source: Fetcher.image(model.image)
|
||||
source: Fetcher.image(model.feed.image)
|
||||
width: height
|
||||
height: parent.height
|
||||
}
|
||||
|
||||
Controls.Label {
|
||||
text: model.name
|
||||
text: model.feed.name
|
||||
height: parent.height
|
||||
anchors.left: icon.right
|
||||
leftPadding: 0.5*Kirigami.Units.gridUnit
|
||||
@ -119,7 +121,7 @@ Kirigami.ScrollablePage {
|
||||
Kirigami.Action {
|
||||
icon.name: "delete"
|
||||
onTriggered: {
|
||||
if(pageStack.depth > 1 && model.url === lastFeed)
|
||||
if(pageStack.depth > 1 && model.feed.url === lastFeed)
|
||||
pageStack.pop()
|
||||
feedListModel.removeFeed(index)
|
||||
}
|
||||
@ -128,8 +130,8 @@ Kirigami.ScrollablePage {
|
||||
]
|
||||
|
||||
onClicked: {
|
||||
lastFeed = model.url
|
||||
pageStack.push("qrc:/EntryListPage.qml", {"name": name, "url": url, "image": image})
|
||||
lastFeed = model.feed.url
|
||||
pageStack.push("qrc:/EntryListPage.qml", {"feed": model.feed})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user