From 6832a7b15594cf778ccedbd6b41f27ff73dffac0 Mon Sep 17 00:00:00 2001 From: Leonardo Robol Date: Sun, 23 Oct 2011 10:55:09 +0200 Subject: [PATCH] Rss fetching is now working, event there is a lot of work to do to make things clear. --- Larss.pro | 8 ++- feedmodel.cpp | 81 ++++++++++++++++++++------- feedmodel.h | 41 +++++++++++++- main.cpp | 9 +-- mainwindow.cpp | 31 +++++++++-- mainwindow.h | 10 +++- mainwindow.ui | 2 +- rssparser.cpp | 173 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ rssparser.h | 84 ++++++++++++++++++++++++++++ 9 files changed, 406 insertions(+), 33 deletions(-) create mode 100644 rssparser.cpp create mode 100644 rssparser.h diff --git a/Larss.pro b/Larss.pro index c242079..5c684a3 100644 --- a/Larss.pro +++ b/Larss.pro @@ -4,7 +4,7 @@ # #------------------------------------------------- -QT += core gui webkit sql +QT += core gui webkit sql network xml TARGET = Larss TEMPLATE = app @@ -12,9 +12,11 @@ TEMPLATE = app SOURCES += main.cpp\ mainwindow.cpp \ - feedmodel.cpp + feedmodel.cpp \ + rssparser.cpp HEADERS += mainwindow.h \ - feedmodel.h + feedmodel.h \ + rssparser.h FORMS += mainwindow.ui diff --git a/feedmodel.cpp b/feedmodel.cpp index 043d11f..f6b4897 100644 --- a/feedmodel.cpp +++ b/feedmodel.cpp @@ -5,31 +5,25 @@ #include #include #include +#include using namespace Larss; -#define FEEDMODEL_MAX_CATEGORIES 1024 - -FeedModel::FeedModel(QObject *parent) : QAbstractItemModel (parent) +FeedModel::FeedModel(QSqlDatabase db, QObject *parent) : QAbstractItemModel (parent) { - db = QSqlDatabase::addDatabase("QSQLITE"); - db.setDatabaseName("/home/leonardo/larss.db"); - db.open(); + this->db = db; } FeedModel::~FeedModel() { - db.close(); } QModelIndex FeedModel::index(int row, int column, const QModelIndex &parent) const { - qDebug () << "Called index for row "<< row << " and column " << column; if (parent.internalId() == 0) { - qDebug () << "That is a category"; QSqlQuery query (db); query.prepare("SELECT id from categories;"); if (query.exec()) @@ -41,8 +35,6 @@ FeedModel::index(int row, int column, const QModelIndex &parent) const if (!query.next ()) return QModelIndex(); } - - qDebug () << "Creating index with id " << query.value(0).toInt(); return createIndex(row, column, query.value(0).toInt()); } else @@ -70,18 +62,16 @@ FeedModel::index(int row, int column, const QModelIndex &parent) const else return QModelIndex(); } - } int FeedModel::rowCount(const QModelIndex &parent) const { - qDebug() << "Called rowCount for parent with index " << parent.internalId(); if (!parent.isValid()) { // Categories count QSqlQuery query(db); - query.prepare ("SELECT id from categories;"); + query.prepare ("SELECT id from categories ORDER by id;"); if (query.exec()) { int row_count = 1; @@ -109,7 +99,6 @@ FeedModel::rowCount(const QModelIndex &parent) const else while (query.next()) row_count++; - qDebug () << "Returning " << row_count << " childs for " << " category " << parent.internalId(); return row_count; } else @@ -126,7 +115,6 @@ FeedModel::columnCount(const QModelIndex &parent) const QVariant FeedModel::data(const QModelIndex &index, int role) const { - qDebug () << "Called data for index" << index.internalId(); if (role == Qt::DisplayRole) { if (index.internalId() == 0) @@ -202,6 +190,7 @@ FeedModel::parent(const QModelIndex &child) const } else { + quint32 category_id; // We have a feed here, that actually has a real parent. // We need to get the ID of the category id -= FEEDMODEL_MAX_CATEGORIES; @@ -210,12 +199,66 @@ FeedModel::parent(const QModelIndex &child) const query.bindValue("id", id); if (query.exec()) { - query.first (); - qDebug () << "Parent of " << id << " is " << query.boundValue(0); - return createIndex (row, 1, query.boundValue(0).toInt()); + if (!query.first ()) + return QModelIndex(); + else + { + category_id = query.value(0).toInt(); + + // We need to get the position of the feed in the category + query.prepare("SELECT id from feeds WHERE category=:category;"); + query.bindValue("category", category_id); + if (query.exec()) + { + row = 1; + if (!query.first()) + return QModelIndex(); + else + { + while (query.next()) + row++; + return createIndex(row, 1, category_id); + } + } + else + return QModelIndex(); + } } else return QModelIndex(); } } +QVariant +FeedModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + // We have a header data only for the first column, horizontal mode. + if (role == Qt::DisplayRole && orientation == Qt::Horizontal && section == 0) + return tr("Feed"); + else + return QVariant (); +} + +QString +FeedModel::getUrl(const QModelIndex &index) +{ + quint64 id = index.internalId(); + if (id < FEEDMODEL_MAX_CATEGORIES) + return ""; + else + { + QSqlQuery query(db); + query.prepare("SELECT url from feeds WHERE id=:id;"); + query.bindValue("id", id - FEEDMODEL_MAX_CATEGORIES); + if (query.exec()) + { + if (query.first()) + return query.value(0).toString(); + else + return ""; + } + else + return ""; + } +} + diff --git a/feedmodel.h b/feedmodel.h index f0e001b..4a1b6ce 100644 --- a/feedmodel.h +++ b/feedmodel.h @@ -7,23 +7,62 @@ namespace Larss { +#define FEEDMODEL_MAX_CATEGORIES 1024 + class FeedModel : public QAbstractItemModel { Q_OBJECT public: - explicit FeedModel(QObject *parent = 0); + explicit FeedModel(QSqlDatabase db, QObject *parent = 0); + + /** + * @brief Destructor for the FeedModel + */ ~FeedModel(); + + /** + * @brief Get the ModelIndex associated with a given row and column. + */ QModelIndex index(int row, int column, const QModelIndex &parent) const; + + /** + * @brief Get the number of rows in the list. + */ int rowCount(const QModelIndex &parent) const; + + /** + * @brief Get the number of column + */ int columnCount(const QModelIndex &parent) const; + + /** + * @brief Get the data associated to a given node. + */ QVariant data(const QModelIndex &index, int role) const; + + /** + * @brief Get the parent of a given node. + */ QModelIndex parent(const QModelIndex &child) const; + /** + * @brief Return the data to be inserted in the column header of the treeview. + */ + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + + /** + * @brief Return the URL associated with a given ModelIndex + */ + QString getUrl (const QModelIndex& index); + signals: public slots: private: + /** + * @brief Database containing the data of the feeds. + */ QSqlDatabase db; diff --git a/main.cpp b/main.cpp index b31e8fc..778716d 100644 --- a/main.cpp +++ b/main.cpp @@ -3,9 +3,10 @@ int main(int argc, char *argv[]) { - QApplication a(argc, argv); - Larss::MainWindow w; - w.show(); + QApplication lars_application(argc, argv); + Larss::MainWindow main_window; - return a.exec(); + main_window.show(); + + return lars_application.exec(); } diff --git a/mainwindow.cpp b/mainwindow.cpp index 8f9bb2c..a99f8ee 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,6 +1,7 @@ #include "mainwindow.h" #include "ui_mainwindow.h" -#include "feedmodel.h" +#include +#include using namespace Larss; @@ -10,13 +11,29 @@ MainWindow::MainWindow(QWidget *parent) : { ui->setupUi(this); - FeedModel *model = new FeedModel (this); - ui->feedTreeView->setModel(model); - ui->feedTreeView->show(); + // Open the database + db = QSqlDatabase::addDatabase("QSQLITE"); + db.setDatabaseName("/home/leonardo/larss.db"); + db.open(); + + // Load feedModel that will wrap the SQLite database + feedModel = new FeedModel(db, this); + ui->feedTreeView->setModel(feedModel); + + // Load the RSSParser, hiding the unnecessary columns + rssParser = new RssParser(db, feedModel, this); + ui->newsTableView->setModel(rssParser); + ui->newsTableView->setSelectionBehavior(QAbstractItemView::SelectRows); + ui->newsTableView->setColumnHidden(0, true); // ID + ui->newsTableView->setColumnHidden(1, true); // Feed ID + ui->newsTableView->setColumnHidden(3, true); // Link + ui->newsTableView->setColumnHidden(6, true); // Read state + } MainWindow::~MainWindow() { + db.close(); delete ui; } @@ -31,3 +48,9 @@ void MainWindow::on_actionExit_activated() // Exit the application do_exit(); } + +void Larss::MainWindow::on_feedTreeView_clicked(const QModelIndex &index) +{ + // Trigger refresh of selected item + rssParser->loadItem(index); +} diff --git a/mainwindow.h b/mainwindow.h index 312292e..6d33ddf 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -2,7 +2,9 @@ #define MAINWINDOW_H #include - +#include "rssparser.h" +#include "feedmodel.h" +#include namespace Ui { class MainWindow; @@ -21,9 +23,15 @@ public: private slots: void on_actionExit_activated(); + void on_feedTreeView_clicked(const QModelIndex &index); + private: Ui::MainWindow *ui; void do_exit(); + + QSqlDatabase db; + FeedModel *feedModel; + RssParser *rssParser; }; } diff --git a/mainwindow.ui b/mainwindow.ui index 9634e55..729df6f 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -25,7 +25,7 @@ Qt::Vertical - + diff --git a/rssparser.cpp b/rssparser.cpp new file mode 100644 index 0000000..1b22011 --- /dev/null +++ b/rssparser.cpp @@ -0,0 +1,173 @@ +#include "rssparser.h" +#include +#include +#include +#include + +using namespace Larss; + +Larss::RssParser::RssParser(QSqlDatabase db, FeedModel *model, QObject *parent) : + QSqlTableModel (parent, db) +{ + // Create the database if it does not exists. + if (!db.tables().contains ("news")) + { + qDebug () << "Creating table news that is not in database..."; + QSqlQuery query(db); + query.prepare("CREATE TABLE news (id INTEGER PRIMARY KEY, feed INTEGER, title TEXT, link TEXT, description TEXT, time INTEGER, read INTEGER);"); + if (!query.exec()) + qDebug () << "Error occurred while creating the database:" << query.lastError(); + } + + // Set init parameters for the QSqlDataTable + setTable("news"); + + // Select manual submit so user cannot modify content directly + setEditStrategy(QSqlTableModel::OnManualSubmit); + + // Set the source to an empty string, that means no source + rssContent = new QHash(); + workQueue = new QList (); + this->model = model; + nowLoading = 0; + activeItem = 0; + + // Create the QNetworkAccessManager and connect the loaded signal + // with our handler. + manager = new QNetworkAccessManager (this); + manager->connect(manager, SIGNAL(finished(QNetworkReply*)), + this, SLOT(networkManagerReplyFinished(QNetworkReply*))); +} + +QVariant +Larss::RssParser::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role == Qt::DisplayRole) + { + if (orientation == Qt::Horizontal) + { + switch (section) + { + case 0: + return tr("ID"); + break; + case 1: + return tr("Feed"); + break; + case 2: + return tr("Title"); + break; + case 3: + return tr("Link"); + break; + case 4: + return tr("Description"); + break; + case 5: + return tr("Time"); + break; + case 6: + return tr("Read"); + } + } + } + + return QVariant (QVariant::Invalid); +} + +Larss::RssParser::~RssParser() +{ + db.close(); + delete manager; + delete rssContent; + delete workQueue; +} + +void +Larss::RssParser::loadItem(const QModelIndex &index) +{ + if (index.internalId() < FEEDMODEL_MAX_CATEGORIES) + return; + + if (nowLoading != 0) + { + // We should queue this work and wait till there is + // the networkmanager free to do it. + workQueue->append(index); + } + else + { + nowLoading = index.internalId(); + manager->get(QNetworkRequest(QUrl(model->getUrl(index)))); + } +} + +void +Larss::RssParser::networkManagerReplyFinished(QNetworkReply *reply) +{ + rssContent->insert (nowLoading, reply->readAll()); + + // Now update the database with the new data obtained. + QDomDocument doc; + if (doc.setContent(rssContent->value(nowLoading))) + { + QDomElement doc_el = doc.documentElement(); + QDomNodeList items = doc_el.elementsByTagName("item"); + + for (quint32 i = 0; i < items.length(); i++) + { + // Get the i-th news + QDomNode item = items.item(i); + QDomElement element = item.toElement(); + + // Get the data in it + QString link = element.elementsByTagName("link").item(0).firstChild().nodeValue(); + QString title = element.elementsByTagName("title").item(0).firstChild().nodeValue(); + QString description = element.elementsByTagName("description").item(0).firstChild().nodeValue(); + + // We should enable this for RSS 2.0 + // QString guid = element.elementsByTagName("guid").item(0).firstChild().nodeValue(); + // QString pubDate = element.elementsByTagName("pubDate").item(0).firstChild().nodeValue(); + + // Try to catch other news_feed with the same link + QSqlQuery query(db); + query.prepare ("SELECT id from news WHERE feed=:feed AND link=:link;"); + query.bindValue("feed", nowLoading); + query.bindValue("link", link); + if (query.exec()) + { + if (!query.first()) + { + // That means that no results were found, so let's insert this one. + QSqlRecord record = this->record(); + record.setValue("time", 0); + record.setValue("read", 0); + record.setValue("title", title); + record.setValue("link", link); + record.setValue("description", description); + record.setValue("feed", nowLoading); + + if (!insertRecord(-1, record)) + qDebug () << "Error inserting record"; + } + } + } + } + else + qDebug () << "Error parsing the document"; + + + if (activeItem == nowLoading) + { + // We shall call something like dataChanged in here + } + nowLoading = 0; + + // Check if there is work in the queue + if (!workQueue->isEmpty()) + { + QModelIndex next_item = workQueue->takeFirst(); + nowLoading = next_item.internalId(); + manager->get(QNetworkRequest(QUrl(model->getUrl(next_item)))); + } +} diff --git a/rssparser.h b/rssparser.h new file mode 100644 index 0000000..df022f2 --- /dev/null +++ b/rssparser.h @@ -0,0 +1,84 @@ +#ifndef RSSPARSER_H +#define RSSPARSER_H + +#include +#include +#include +#include +#include "feedmodel.h" + +namespace Larss { + + class RssParser : public QSqlTableModel + { + Q_OBJECT + public: + /** + * @brief RssParser constructor. + */ + explicit RssParser(QSqlDatabase db, FeedModel * model = NULL, QObject *parent = 0); + + /** + * @brief Default destructor. + */ + ~RssParser(); + + /** + * @brief Reload the feed from a given NewsFeed. + */ + void loadItem (const QModelIndex& index); + + /** + * @brief Function that tells the views that use this model + * what to display in the title of the columns. + */ + QVariant headerData (int section, Qt::Orientation orientation, int role) const; + + signals: + + public slots: + void networkManagerReplyFinished (QNetworkReply* reply); + + private: + /** + * @brief Database where all the news will be loaded and saved. + */ + QSqlDatabase db; + + /** + * @brief The url of the current rss feed. + */ + FeedModel *model; + + /** + * @brief The content of the rss loaded from the various + * items in the feedmodel. + */ + QHash *rssContent; + + /** + * @brief The NetworkAccessManager that will be used to retrieve + * the data from sourceUrl. + */ + QNetworkAccessManager *manager; + + /** + * @brief An index of the RSS that is currently being loaded, or 0 + * if there is nothing in the queue. + */ + quint32 nowLoading; + + /** + * @brie A pointer to the active news feed. + */ + quint32 activeItem; + + /** + * @brief Queue of item that needs to be refreshed. + */ + QList *workQueue; + }; + +} + +#endif // RSSPARSER_H -- 2.1.4