VERY initial and thin implementation of standard feed fetching.

This commit is contained in:
Martin Rotter 2013-12-23 18:25:01 +01:00
parent e717e57813
commit 6476552aaa
13 changed files with 229 additions and 30 deletions

View File

@ -278,6 +278,7 @@ set(APP_SOURCES
src/core/feedsmodelstandardfeed.cpp
src/core/parsingfactory.cpp
src/core/feeddownloader.cpp
src/core/networkfactory.cpp
# Basic application sources.
src/main.cpp

View File

@ -32,6 +32,7 @@ void Debugging::debugHandler(QtMsgType type,
const QString &message) {
#ifndef QT_NO_DEBUG_OUTPUT
const char *file = qPrintable(QString(placement.file).section(QDir::separator(), -1));
switch (type) {
case QtDebugMsg:
DEBUG_OUTPUT_WORKER("INFO", file, placement.line, message);

View File

@ -1,17 +1,12 @@
#include "core/feeddownloader.h"
#include "core/feedsmodelfeed.h"
#include "core/systemfactory.h"
#include "core/silentnetworkaccessmanager.h"
#include <QThread>
#include <QDebug>
#include <QReadWriteLock>
#include <QApplication>
QPointer<SilentNetworkAccessManager> FeedDownloader::m_networkManager;
FeedDownloader::FeedDownloader(QObject *parent) : QObject(parent) {
}
@ -19,14 +14,6 @@ FeedDownloader::~FeedDownloader() {
qDebug("Destroying FeedDownloader instance.");
}
SilentNetworkAccessManager *FeedDownloader::globalNetworkManager() {
if (m_networkManager.isNull()) {
m_networkManager = new SilentNetworkAccessManager(qApp);
}
return m_networkManager;
}
void FeedDownloader::updateFeeds(const QList<FeedsModelFeed *> &feeds) {
qDebug().nospace() << "Performing feed updates in thread: \'" <<
QThread::currentThreadId() << "\'.";

View File

@ -19,12 +19,6 @@ class FeedDownloader : public QObject {
explicit FeedDownloader(QObject *parent = 0);
virtual ~FeedDownloader();
// Returns pointer to global network access manager
// for feed online operations (primarily fetchich of new messages).
// NOTE: All feed online operations shar network access manager,
// which makes setting of custom network settings easy.
static SilentNetworkAccessManager *globalNetworkManager();
signals:
// Emitted if all items from update queue are
// processed.
@ -42,9 +36,6 @@ class FeedDownloader : public QObject {
// are stored persistently in the database.
// Appropriate signals are emitted.
void updateFeeds(const QList<FeedsModelFeed*> &feeds);
private:
static QPointer<SilentNetworkAccessManager> m_networkManager;
};
#endif // FEEDDOWNLOADER_H

View File

@ -1,12 +1,16 @@
#include "core/feedsmodelstandardfeed.h"
#include "core/defs.h"
#include "core/settings.h"
#include "core/parsingfactory.h"
#include "core/databasefactory.h"
#include "core/networkfactory.h"
#include "gui/iconfactory.h"
#include "gui/iconthemefactory.h"
#include <QVariant>
#include <QThread>
#include <unistd.h>
#include <QTextCodec>
#include <QSqlQuery>
FeedsModelStandardFeed::FeedsModelStandardFeed(FeedsModelRootItem *parent_item)
@ -137,5 +141,101 @@ QVariant FeedsModelStandardFeed::data(int column, int role) const {
}
void FeedsModelStandardFeed::update() {
usleep(5500000);
QByteArray feed_contents;
int download_timeout = Settings::getInstance()->value(APP_CFG_FEEDS,
"download_timeout",
5000).toInt();
QNetworkReply::NetworkError download_result = NetworkFactory::downloadFile(url(),
download_timeout,
feed_contents);
if (download_result != QNetworkReply::NoError) {
qWarning("Error during fetching of new messages for feed '%s' (id %d).",
qPrintable(url()),
id());
return;
}
// Encode downloaded data for further parsing.
QTextCodec *codec = QTextCodec::codecForName(encoding().toLocal8Bit());
QString formatted_feed_contents;
if (codec == NULL) {
// No suitable codec for this encoding was found.
// Use non-converted data.
formatted_feed_contents = feed_contents;
}
else {
formatted_feed_contents = codec->toUnicode(feed_contents);
}
// Feed data are downloaded and encoded.
// Parse data and obtain messages.
QList<Message> messages;
switch (type()) {
case FeedsModelFeed::StandardRss2X:
messages = ParsingFactory::parseAsRSS20(formatted_feed_contents);
break;
// TODO: Add support for other standard formats.
default:
break;
}
updateMessages(messages);
}
void FeedsModelStandardFeed::updateMessages(const QList<Message> &messages) {
int feed_id = id(), message_id;
QSqlDatabase database = DatabaseFactory::getInstance()->addConnection("FeedsModelStandardFeed");
// Prepare queries.
QSqlQuery query_select(database);
QSqlQuery query_insert(database);
query_select.prepare("SELECT id, feed, date_created FROM Messages "
"WHERE feed = :feed AND title = :title AND url = :url;");
query_insert.prepare("INSERT INTO Messages "
"(feed, title, url, author, date_created, date_updated, contents) "
"VALUES (:feed, :title, :url, :author, :date_created, :date_updated, :contents);");
foreach (const Message &message, messages) {
query_select.bindValue(":feed", feed_id);
query_select.bindValue(":title", message.m_title);
query_select.bindValue(":url", message.m_url);
query_select.exec();
if (query_select.next()) {
// Message with this title & url probably exists in current feed.
message_id = query_select.value(0).toInt();
}
else {
message_id = -1;
}
query_select.finish();
if (message_id == -1) {
// Message is not fetched in this feed yet. Add it.
query_insert.bindValue(":feed", feed_id);
query_insert.bindValue(":title", message.m_title);
query_insert.bindValue(":url", message.m_url);
query_insert.bindValue(":author", message.m_author);
query_insert.bindValue(":date_created", message.m_created.toString(Qt::ISODate));
query_insert.bindValue(":date_updated", message.m_updated.toString(Qt::ISODate));
query_insert.bindValue(":contents", message.m_contents);
query_insert.exec();
query_insert.finish();
}
else {
// Message is already persistently stored.
// TODO: Update message if it got updated in the
// online feed.
}
}
}

View File

@ -7,6 +7,8 @@
#include <QSqlRecord>
class Message;
// Represents STANDARD RSS/RDF/ATOM feed with no
// online synchronization services (NO TT-RSS, NO FEEDLY).
// So, parent item is either root item or category.
@ -40,6 +42,12 @@ class FeedsModelStandardFeed : public FeedsModelFeed {
// Loads standard feed object from given SQL record.
static FeedsModelStandardFeed *loadFromRecord(const QSqlRecord &record);
protected:
// Persistently stores given messages into the database
// and updates existing messages if newer version is
// available.
void updateMessages(const QList<Message> &messages);
private:
QDateTime m_creationDate;
QString m_encoding;

View File

@ -135,8 +135,13 @@ QVariant MessagesModel::data(const QModelIndex &index, int role) const {
// Human readable data for viewing.
case Qt::DisplayRole: {
int index_column = index.column();
if (index_column != MSG_DB_IMPORTANT_INDEX &&
index_column != MSG_DB_READ_INDEX) {
if (index_column == MSG_DB_DUPDATED_INDEX) {
// This column contains QDateTime.
return TextFactory::parseDateTime(QSqlTableModel::data(index, role).toString()).toString(Qt::DefaultLocaleShortDate);
}
else if (index_column != MSG_DB_IMPORTANT_INDEX &&
index_column != MSG_DB_READ_INDEX) {
return QSqlTableModel::data(index, role);
}
else {

View File

@ -0,0 +1,86 @@
#include "core/networkfactory.h"
#include "core/silentnetworkaccessmanager.h"
#include <QEventLoop>
#include <QTimer>
NetworkFactory::NetworkFactory() {
}
QNetworkReply::NetworkError NetworkFactory::downloadFile(const QString &url,
int timeout,
QByteArray &output) {
// Original asynchronous behavior of QNetworkAccessManager
// is replaced by synchronous behavior in order to make
// process of downloading of a file easier to understand.
// Make necessary variables.
SilentNetworkAccessManager manager;
QEventLoop loop;
QTimer timer;
QNetworkRequest request;
QNetworkReply *reply;
// Set url for this reques.
request.setUrl(url);
// Create necessary connections.
QObject::connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
QObject::connect(&manager, SIGNAL(finished(QNetworkReply*)), &loop, SLOT(quit()));
forever {
// This timer fires just ONCE.
timer.setSingleShot(true);
// Try to open communication channel.
reply = manager.get(request);
// Start the timeout timer.
timer.start(timeout);
// Enter the event loop.
loop.exec();
// At this point one of two things happened:
// a) file download was completed,
// b) communication timed-out.
if (timer.isActive()) {
// Download is complete because timer is still running.
timer.stop();
}
else {
reply->deleteLater();
// Timer already fired. Download is NOT successful.
return QNetworkReply::TimeoutError;
}
// In this phase, some part of downloading process is completed.
if (reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl().isValid()) {
// Communication indicates that HTTP redirection is needed.
request.setUrl(reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl());
}
else {
// No redirection is indicated. Final file is obtained
// in our "reply" object.
break;
}
}
// Read the data into output buffer.
output = reply->readAll();
QNetworkReply::NetworkError reply_error = reply->error();
qDebug("File '%s' fetched with status %d.",
qPrintable(url),
reply_error);
// Delete needed stuff and exit.
reply->deleteLater();
return reply_error;
}

20
src/core/networkfactory.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef NETWORKFACTORY_H
#define NETWORKFACTORY_H
#include <QNetworkReply>
class NetworkFactory {
private:
explicit NetworkFactory();
public:
// Performs SYNCHRONOUS download of file with given URL
// and given timeout.
static QNetworkReply::NetworkError downloadFile(const QString &url,
int timeout,
QByteArray &output);
};
#endif // NETWORKFACTORY_H

View File

@ -49,6 +49,7 @@ QList<Message> ParsingFactory::parseAsRSS20(const QString &data) {
}
new_message.m_updated = TextFactory::parseDateTime(elem_updated.text());
new_message.m_created = new_message.m_updated;
messages.append(new_message);
}

View File

@ -68,7 +68,8 @@ QSettings::Status Settings::setupSettings() {
s_instance = new Settings(app_path_file, QSettings::IniFormat,
Settings::Portable, qApp);
// TODO: Separate web settings into another unit.
// TODO: Separate web settings into another unit if
// MORE web/network-related settings will be needed.
// Construct icon cache in the same path.
QString web_path = app_path + QDir::separator() + QString(APP_DB_WEB_PATH);
QDir(web_path).mkpath(web_path);

View File

@ -16,7 +16,6 @@ class SilentNetworkAccessManager : public BaseNetworkAccessManager {
protected slots:
void onSslErrors(QNetworkReply *reply, const QList<QSslError> &error);
void onAuthenticationRequired(QNetworkReply * reply, QAuthenticator *authenticator);
};
#endif // SILENTNETWORKACCESSMANAGES_H

View File

@ -365,7 +365,6 @@ void FormSettings::saveProxy() {
// Reload settings for all network access managers.
WebBrowser::globalNetworkManager()->loadSettings();
FeedDownloader::globalNetworkManager()->loadSettings();
}
void FormSettings::loadLanguage() {