Advanced implementation of auto-fetch-guess feature.

This commit is contained in:
Martin Rotter 2014-02-09 15:10:54 +01:00
parent 36555ee943
commit fb90fc91da
7 changed files with 173 additions and 23 deletions

View File

@ -5,17 +5,6 @@
#include <QPointer>
#include <QSqlDatabase>
// TODO: přidat podporu pro mysql
// nemužu mit stejny SQL kod pro mysql a sqlite
// ale musim docilit aby oba kody SQL delaly tabulky
// se stejnymi atributy - stejnymi nazvy sloupcu
// PROBLEMY se mysql: nutno pridat AUTO_INCREMENT u int primary keyů
// taky bacha na nazvy sloupců, třeba
// key a read sou klicovy slouva a fejlne to tak
// taky bacha ze typ TEXT nemuze bejt dost dobre
// pouzitej v UNIQUE CHECK, misto toho se da pouzit treba
// VARCHAR (100)
class DatabaseFactory : public QObject {
Q_OBJECT

View File

@ -13,6 +13,11 @@
#include <QTextCodec>
#include <QSqlQuery>
#include <QDomDocument>
#include <QDomNode>
#include <QDomElement>
#include <QXmlStreamReader>
FeedsModelStandardFeed::FeedsModelStandardFeed(FeedsModelRootItem *parent_item)
: FeedsModelFeed(parent_item),
@ -44,15 +49,117 @@ FeedsModelStandardFeed *FeedsModelStandardFeed::loadFromRecord(const QSqlRecord
return feed;
}
FeedsModelStandardFeed *FeedsModelStandardFeed::guessFeed(const QString &url,
const QString &username,
const QString &password) {
// TODO: http://www.google.com/s2/favicons?domain=root.cz
// ZISKAT ikonu (napsat taky aby se dala ikona pro
// dane url ziskavat taky samostatne
// pak ziskat informace o kanalu
QPair<FeedsModelStandardFeed*, QNetworkReply::NetworkError> FeedsModelStandardFeed::guessFeed(const QString &url,
const QString &username,
const QString &password) {
QPair<FeedsModelStandardFeed*, QNetworkReply::NetworkError> result; result.first = NULL;
return NULL;
// Try to obtain icon.
QIcon icon_data;
if (NetworkFactory::downloadIcon(url,
5000,
icon_data) == QNetworkReply::NoError) {
// Icon for feed was downloaded and is stored now in _icon_data.
result.first = new FeedsModelStandardFeed();
result.first->setIcon(icon_data);
}
QByteArray feed_contents;
if ((result.second = NetworkFactory::downloadFeedFile(url,
Settings::instance()->value(APP_CFG_FEEDS, "feed_update_timeout", DOWNLOAD_TIMEOUT).toInt(),
feed_contents,
true,
username,
password)) == QNetworkReply::NoError) {
// Feed XML was obtained, now we need to try to guess
// its encoding before we can read further data.
QXmlStreamReader xml_stream_reader(feed_contents);
QString xml_schema_encoding;
QString xml_contents_encoded;
// We have several chances to read the XML version directly
// from XML declaration.
for (int i = 0; i < 2 && !xml_stream_reader.atEnd(); i++) {
if ((xml_schema_encoding = xml_stream_reader.documentEncoding().toString()).isEmpty()) {
xml_stream_reader.readNext();
}
else {
break;
}
}
QTextCodec *custom_codec = QTextCodec::codecForName(xml_schema_encoding.toLocal8Bit());
if (custom_codec != NULL) {
// Feed encoding was probably guessed.
xml_contents_encoded = custom_codec->toUnicode(feed_contents);
result.first->setEncoding(xml_schema_encoding);
}
else {
// Feed encoding probably not guessed, set it as
// default.
xml_contents_encoded = feed_contents;
result.first->setEncoding(DEFAULT_FEED_ENCODING);
}
// Feed XML was obtained, guess it now.
QDomDocument xml_document;
if (!xml_document.setContent(xml_contents_encoded)) {
// XML is invalid, exit.
return result;
}
QDomElement root_element = xml_document.documentElement();
QString root_tag_name = root_element.tagName();
if (root_tag_name == "rdf:RDF") {
if (result.first == NULL) {
result.first = new FeedsModelStandardFeed();
}
// We found RDF feed.
QDomElement channel_element = root_element.namedItem("channel").toElement();
result.first->setType(StandardRdf);
result.first->setTitle(channel_element.namedItem("title").toElement().text());
result.first->setDescription(channel_element.namedItem("description").toElement().text());
}
else if (root_tag_name == "rss") {
if (result.first == NULL) {
result.first = new FeedsModelStandardFeed();
}
// We found RSS 0.91/0.92/0.93/2.0/2.0.1 feed.
QString rss_type = root_element.attribute("version", "2.0");
if (rss_type == "0.91" || rss_type == "0.92" || rss_type == "0.93") {
result.first->setType(StandardRss0X);
}
else {
result.first->setType(StandardRss2X);
}
QDomElement channel_element = root_element.namedItem("channel").toElement();
result.first->setTitle(channel_element.namedItem("title").toElement().text());
result.first->setDescription(channel_element.namedItem("description").toElement().text());
}
else if (root_tag_name == "feed") {
if (result.first == NULL) {
result.first = new FeedsModelStandardFeed();
}
// We found ATOM feed.
result.first->setType(StandardAtom10);
result.first->setTitle(root_element.namedItem("title").toElement().text());
result.first->setDescription(root_element.namedItem("subtitle").toElement().text());
}
}
return result;
}
QVariant FeedsModelStandardFeed::data(int column, int role) const {

View File

@ -5,6 +5,8 @@
#include <QDateTime>
#include <QSqlRecord>
#include <QPair>
#include <QNetworkReply>
class Message;
@ -83,10 +85,12 @@ class FeedsModelStandardFeed : public FeedsModelFeed {
// Tries to guess feed hidden under given URL
// and uses given credentials.
// Returns NULL if something failed.
static FeedsModelStandardFeed *guessFeed(const QString &url,
const QString &username,
const QString &password);
// Returns pointer to guessed feed (if at least partially
// guessed) and retrieved error/status code from network layer
// or NULL feed.
static QPair<FeedsModelStandardFeed*, QNetworkReply::NetworkError> guessFeed(const QString &url,
const QString &username,
const QString &password);
protected:
// Persistently stores given messages into the database

View File

@ -5,11 +5,31 @@
#include <QEventLoop>
#include <QTimer>
#include <QTextDocument>
NetworkFactory::NetworkFactory() {
}
QNetworkReply::NetworkError NetworkFactory::downloadIcon(const QString &url,
int timeout,
QIcon &output) {
QString google_s2_with_url = QString("http://www.google.com/s2/favicons?domain=%1").arg(Qt::escape(url));
QByteArray icon_data;
QNetworkReply::NetworkError network_result = downloadFeedFile(google_s2_with_url,
timeout,
icon_data);
if (network_result == QNetworkReply::NoError) {
QPixmap icon_pixmap;
icon_pixmap.loadFromData(icon_data);
output = QIcon(icon_pixmap);
}
return network_result;
}
QNetworkReply::NetworkError NetworkFactory::downloadFeedFile(const QString &url,
int timeout,
QByteArray &output,

View File

@ -12,6 +12,12 @@ class NetworkFactory {
explicit NetworkFactory();
public:
// Performs SYNCHRONOUS download if favicon for the site,
// given URL belongs to.
static QNetworkReply::NetworkError downloadIcon(const QString &url,
int timeout,
QIcon &output);
// Performs SYNCHRONOUS download of file with given URL
// and given timeout.
static QNetworkReply::NetworkError downloadFeedFile(const QString &url,

View File

@ -16,6 +16,8 @@
#include <QTextCodec>
#include <QFileDialog>
#include <QMenu>
#include <QPair>
#include <QNetworkReply>
FormStandardFeedDetails::FormStandardFeedDetails(FeedsModel *model, QWidget *parent)
@ -240,6 +242,25 @@ void FormStandardFeedDetails::apply() {
}
}
void FormStandardFeedDetails::guessFeed() {
QPair<FeedsModelStandardFeed*, QNetworkReply::NetworkError> result = FeedsModelStandardFeed::guessFeed(m_ui->m_txtUrl->lineEdit()->text(),
m_ui->m_txtUsername->lineEdit()->text(),
m_ui->m_txtPassword->lineEdit()->text());
if (result.first != NULL) {
// Icon was perhaps guessed.
m_ui->m_btnIcon->setIcon(result.first->icon());
m_ui->m_txtTitle->lineEdit()->setText(result.first->title());
m_ui->m_txtDescription->lineEdit()->setText(result.first->description());
m_ui->m_cmbType->setCurrentIndex(m_ui->m_cmbType->findData(QVariant::fromValue((int) result.first->type())));
m_ui->m_cmbEncoding->setCurrentIndex(m_ui->m_cmbEncoding->findData(result.first->encoding(), Qt::DisplayRole));
}
else {
// No feed guessed, even no icon available.
}
}
void FormStandardFeedDetails::createConnections() {
// General connections.
connect(m_ui->m_buttonBox, SIGNAL(accepted()),
@ -258,6 +279,8 @@ void FormStandardFeedDetails::createConnections() {
this, SLOT(onAuthenticationSwitched()));
connect(m_ui->m_cmbAutoUpdateType, SIGNAL(currentIndexChanged(int)),
this, SLOT(onAutoUpdateTypeChanged(int)));
connect(m_btnLoadDataFromInternet, SIGNAL(clicked()),
this, SLOT(guessFeed()));
// Icon connections.
connect(m_actionLoadIconFromFile, SIGNAL(triggered()), this, SLOT(onLoadIconFromFile()));

View File

@ -30,6 +30,7 @@ class FormStandardFeedDetails : public QDialog {
protected slots:
// Applies changes.
void apply();
void guessFeed();
// Trigerred when title/description/url/username/password changes.
void onTitleChanged(const QString &new_title);