VERY initial and thin implementation of standard feed fetching.
This commit is contained in:
parent
e717e57813
commit
6476552aaa
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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() << "\'.";
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
86
src/core/networkfactory.cpp
Normal file
86
src/core/networkfactory.cpp
Normal 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
20
src/core/networkfactory.h
Normal 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
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -365,7 +365,6 @@ void FormSettings::saveProxy() {
|
||||
|
||||
// Reload settings for all network access managers.
|
||||
WebBrowser::globalNetworkManager()->loadSettings();
|
||||
FeedDownloader::globalNetworkManager()->loadSettings();
|
||||
}
|
||||
|
||||
void FormSettings::loadLanguage() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user