Rss fetching is now working, event there is a lot of work to do to make

Leonardo Robol [2011-10-23 08:55]
Rss fetching is now working, event there is a lot of work to do to make
things clear.
Filename
Larss.pro
feedmodel.cpp
feedmodel.h
main.cpp
mainwindow.cpp
mainwindow.h
mainwindow.ui
rssparser.cpp
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 <QSqlError>
 #include <QDebug>
 #include <QStringList>
+#include <QLabel>

 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 <QDebug>
+#include <QtGui>

 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 <QMainWindow>
-
+#include "rssparser.h"
+#include "feedmodel.h"
+#include <QSqlDatabase>

 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 @@
        <property name="orientation">
         <enum>Qt::Vertical</enum>
        </property>
-       <widget class="QListView" name="newsListView"/>
+       <widget class="QTableView" name="newsTableView"/>
        <widget class="QWebView" name="webView">
         <property name="url">
          <url>
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 <QDebug>
+#include <QtXml>
+#include <QtSql>
+#include <QtNetwork>
+
+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<quint32, QString>();
+    workQueue = new QList<QModelIndex> ();
+    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 <QObject>
+#include <QtNetwork/QNetworkAccessManager>
+#include <QList>
+#include <QSqlTableModel>
+#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<quint32, QString> *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<QModelIndex> *workQueue;
+    };
+
+}
+
+#endif // RSSPARSER_H
ViewGit