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