diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fd3b67e6..55af41da 100755 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,8 +3,6 @@ set(alligator_SRCS feedListModel.cpp entryListModel.cpp fetcher.cpp - feed.cpp - entry.cpp database.cpp resources.qrc ) diff --git a/src/database.cpp b/src/database.cpp index ebe582eb..5d7b9166 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -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);"))); diff --git a/src/entry.cpp b/src/entry.cpp deleted file mode 100644 index 874bb172..00000000 --- a/src/entry.cpp +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright 2020 Tobias Fella - * - * 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 . - */ - -#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; -} diff --git a/src/entry.h b/src/entry.h deleted file mode 100644 index b92119ab..00000000 --- a/src/entry.h +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright 2020 Tobias Fella - * - * 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 . - */ - -#pragma once - -#include - -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; -}; diff --git a/src/entryListModel.cpp b/src/entryListModel.cpp index aa37c70d..eae94024 100644 --- a/src/entryListModel.cpp +++ b/src/entryListModel.cpp @@ -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 EntryListModel::roleNames() const { QHash 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({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); } diff --git a/src/entryListModel.h b/src/entryListModel.h index e3d2d19c..d41797f5 100644 --- a/src/entryListModel.h +++ b/src/entryListModel.h @@ -20,41 +20,32 @@ #pragma once -#include +#include #include +#include -#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 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 m_entries; - Feed m_feed; - - void update(); + QString m_feed; }; diff --git a/src/feed.cpp b/src/feed.cpp deleted file mode 100644 index c295d541..00000000 --- a/src/feed.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright 2020 Tobias Fella - * - * 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 . - */ - -#include - -#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; -} diff --git a/src/feed.h b/src/feed.h deleted file mode 100644 index d1bddb8d..00000000 --- a/src/feed.h +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright 2020 Tobias Fella - * - * 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 . - */ - -#pragma once - -#include -#include - -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); diff --git a/src/feedListModel.cpp b/src/feedListModel.cpp index 7a606317..7a0e75ad 100644 --- a/src/feedListModel.cpp +++ b/src/feedListModel.cpp @@ -19,86 +19,77 @@ */ #include +#include +#include +#include +#include #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 FeedListModel::roleNames() const { QHash 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(); } diff --git a/src/feedListModel.h b/src/feedListModel.h index 31950c50..01298c5b 100644 --- a/src/feedListModel.h +++ b/src/feedListModel.h @@ -20,29 +20,30 @@ #pragma once -#include +#include #include #include -#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 roleNames() const override; Q_INVOKABLE void addFeed(QString url); Q_INVOKABLE void removeFeed(int index); private: - QVector feeds; + bool feedExists(QString url); }; diff --git a/src/fetcher.cpp b/src/fetcher.cpp index e5a6136c..797399e4 100644 --- a/src/fetcher.cpp +++ b/src/fetcher.cpp @@ -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(); }); } diff --git a/src/fetcher.h b/src/fetcher.h index 3447e064..7bcb78be 100644 --- a/src/fetcher.h +++ b/src/fetcher.h @@ -39,5 +39,5 @@ private: Fetcher(const Fetcher &); Q_SIGNALS: - void finished(); + void updated(); }; diff --git a/src/main.cpp b/src/main.cpp index 493ff784..a75e3f10 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -43,7 +43,6 @@ Q_DECL_EXPORT int main(int argc, char *argv[]) qmlRegisterType("org.kde.alligator", 1, 0, "FeedListModel"); qmlRegisterType("org.kde.alligator", 1, 0, "EntryListModel"); - qRegisterMetaType(); QQmlApplicationEngine engine; diff --git a/src/qml/EntryListPage.qml b/src/qml/EntryListPage.qml index 9a6a15cc..1edf957f 100755 --- a/src/qml/EntryListPage.qml +++ b/src/qml/EntryListPage.qml @@ -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 } diff --git a/src/qml/FeedListPage.qml b/src/qml/FeedListPage.qml index f093b9e5..7d577051 100644 --- a/src/qml/FeedListPage.qml +++ b/src/qml/FeedListPage.qml @@ -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}) } } }