// This file is part of RSS Guard. // // Copyright (C) 2011-2017 by Martin Rotter // // RSS Guard is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // RSS Guard is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with RSS Guard. If not, see . #include "services/standard/standardfeed.h" #include "core/feedsmodel.h" #include "definitions/definitions.h" #include "gui/feedmessageviewer.h" #include "gui/feedsview.h" #include "miscellaneous/databasequeries.h" #include "miscellaneous/iconfactory.h" #include "miscellaneous/settings.h" #include "miscellaneous/simplecrypt/simplecrypt.h" #include "miscellaneous/textfactory.h" #include "network-web/networkfactory.h" #include "services/abstract/recyclebin.h" #include "services/standard/atomparser.h" #include "services/standard/gui/formstandardfeeddetails.h" #include "services/standard/rdfparser.h" #include "services/standard/rssparser.h" #include "services/standard/standardserviceroot.h" #include #include #include #include #include #include #include StandardFeed::StandardFeed(RootItem* parent_item) : Feed(parent_item) { m_passwordProtected = false; m_username = QString(); m_password = QString(); m_networkError = QNetworkReply::NoError; m_type = Rss0X; m_encoding = QString(); } StandardFeed::StandardFeed(const StandardFeed& other) : Feed(other) { m_passwordProtected = other.passwordProtected(); m_username = other.username(); m_password = other.password(); m_networkError = other.networkError(); m_type = other.type(); m_encoding = other.encoding(); } StandardFeed::~StandardFeed() { qDebug("Destroying Feed instance."); } QList StandardFeed::contextMenu() { return serviceRoot()->getContextMenuForFeed(this); } bool StandardFeed::canBeEdited() const { return true; } bool StandardFeed::canBeDeleted() const { return true; } StandardServiceRoot* StandardFeed::serviceRoot() const { return qobject_cast(getParentServiceRoot()); } bool StandardFeed::editViaGui() { QScopedPointer form_pointer(new FormStandardFeedDetails(serviceRoot(), qApp->mainFormWidget())); form_pointer.data()->addEditFeed(this, nullptr); return false; } bool StandardFeed::deleteViaGui() { if (removeItself()) { serviceRoot()->requestItemRemoval(this); return true; } else { return false; } } QVariant StandardFeed::data(int column, int role) const { switch (role) { case Qt::ToolTipRole: if (column == FDS_MODEL_TITLE_INDEX) { //: Tooltip for feed. return tr("%1 (%2)" "%3\n\n" "Network status: %6\n" "Encoding: %4\n" "Auto-update status: %5").arg(title(), StandardFeed::typeToString(type()), description().isEmpty() ? QString() : QString('\n') + description(), encoding(), getAutoUpdateStatusDescription(), NetworkFactory::networkErrorText(m_networkError)); } else { return Feed::data(column, role); } default: return Feed::data(column, role); } } QString StandardFeed::typeToString(StandardFeed::Type type) { switch (type) { case Atom10: return QSL("ATOM 1.0"); case Rdf: return QSL("RDF (RSS 1.0)"); case Rss0X: return QSL("RSS 0.91/0.92/0.93"); case Rss2X: default: return QSL("RSS 2.0/2.0.1"); } } void StandardFeed::fetchMetadataForItself() { QPair metadata = guessFeed(url(), username(), password()); if (metadata.first != nullptr && metadata.second == QNetworkReply::NoError) { // Some properties are not updated when new metadata are fetched. metadata.first->setParent(parent()); metadata.first->setUrl(url()); metadata.first->setPasswordProtected(passwordProtected()); metadata.first->setUsername(username()); metadata.first->setPassword(password()); metadata.first->setAutoUpdateType(autoUpdateType()); metadata.first->setAutoUpdateInitialInterval(autoUpdateInitialInterval()); editItself(metadata.first); delete metadata.first; // Notify the model about fact, that it needs to reload new information about // this item, particularly the icon. serviceRoot()->itemChanged(QList() << this); } else { qApp->showGuiMessage(tr("Metadata not fetched"), tr("Metadata was not fetched because: %1.").arg(NetworkFactory::networkErrorText(metadata.second)), QSystemTrayIcon::Critical); } } QPair StandardFeed::guessFeed(const QString& url, const QString& username, const QString& password) { QPair result; result.first = nullptr; QByteArray feed_contents; NetworkResult network_result = NetworkFactory::performNetworkOperation(url, qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(), QByteArray(), QString(), feed_contents, QNetworkAccessManager::GetOperation, !username.isEmpty(), username, password, true); result.second = network_result.first; if (result.second == QNetworkReply::NoError || !feed_contents.isEmpty()) { // Feed XML was obtained, now we need to try to guess // its encoding before we can read further data. QString xml_schema_encoding; QString xml_contents_encoded; QRegExp encoding_rexp(QSL("encoding=\"[^\"]\\S+\"")); if (encoding_rexp.indexIn(feed_contents) != -1 && !(xml_schema_encoding = encoding_rexp.cap(0)).isEmpty()) { // Some "encoding" attribute was found get the encoding // out of it. encoding_rexp.setPattern(QSL("[^\"]\\S+[^\"]")); encoding_rexp.indexIn(xml_schema_encoding, 9); xml_schema_encoding = encoding_rexp.cap(0); } if (result.first == nullptr) { result.first = new StandardFeed(); } QTextCodec* custom_codec = QTextCodec::codecForName(xml_schema_encoding.toLocal8Bit()); if (custom_codec != nullptr) { // 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; QString error_msg; int error_line, error_column; if (!xml_document.setContent(xml_contents_encoded, &error_msg, &error_line, &error_column)) { qDebug("XML of feed '%s' is not valid and cannot be loaded. Error: '%s' " "(line %d, column %d).", qPrintable(url), qPrintable(error_msg), error_line, error_column); result.second = QNetworkReply::UnknownContentError; // XML is invalid, exit. return result; } QDomElement root_element = xml_document.documentElement(); QString root_tag_name = root_element.tagName(); QList icon_possible_locations; icon_possible_locations.append(url); if (root_tag_name == QL1S("rdf:RDF")) { // We found RDF feed. QDomElement channel_element = root_element.namedItem(QSL("channel")).toElement(); result.first->setType(Rdf); result.first->setTitle(channel_element.namedItem(QSL("title")).toElement().text()); result.first->setDescription(channel_element.namedItem(QSL("description")).toElement().text()); QString source_link = channel_element.namedItem(QSL("link")).toElement().text(); if (!source_link.isEmpty()) { icon_possible_locations.prepend(source_link); } } else if (root_tag_name == QL1S("rss")) { // 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 == QL1S("0.91") || rss_type == QL1S("0.92") || rss_type == QL1S("0.93")) { result.first->setType(Rss0X); } else { result.first->setType(Rss2X); } QDomElement channel_element = root_element.namedItem(QSL("channel")).toElement(); result.first->setTitle(channel_element.namedItem(QSL("title")).toElement().text()); result.first->setDescription(channel_element.namedItem(QSL("description")).toElement().text()); QString source_link = channel_element.namedItem(QSL("link")).toElement().text(); if (!source_link.isEmpty()) { icon_possible_locations.prepend(source_link); } } else if (root_tag_name == QL1S("feed")) { // We found ATOM feed. result.first->setType(Atom10); result.first->setTitle(root_element.namedItem(QSL("title")).toElement().text()); result.first->setDescription(root_element.namedItem(QSL("subtitle")).toElement().text()); QString source_link = root_element.namedItem(QSL("link")).toElement().text(); if (!source_link.isEmpty()) { icon_possible_locations.prepend(source_link); } } else { // File was downloaded and it really was XML file // but feed format was NOT recognized. result.second = QNetworkReply::UnknownContentError; } // Try to obtain icon. QIcon icon_data; if ((result.second = NetworkFactory::downloadIcon(icon_possible_locations, DOWNLOAD_TIMEOUT, icon_data)) == QNetworkReply::NoError) { // Icon for feed was downloaded and is stored now in _icon_data. result.first->setIcon(icon_data); } } return result; } Qt::ItemFlags StandardFeed::additionalFlags() const { return Qt::ItemIsDragEnabled; } bool StandardFeed::performDragDropChange(RootItem* target_item) { StandardFeed* feed_new = new StandardFeed(*this); feed_new->setParent(target_item); if (editItself(feed_new)) { serviceRoot()->requestItemReassignment(this, target_item); delete feed_new; return true; } else { delete feed_new; return false; } } bool StandardFeed::removeItself() { QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); return DatabaseQueries::deleteFeed(database, customId().toInt(), getParentServiceRoot()->accountId()); } bool StandardFeed::addItself(RootItem* parent) { // Now, add feed to persistent storage. QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); bool ok; int new_id = DatabaseQueries::addFeed(database, parent->id(), parent->getParentServiceRoot()->accountId(), title(), description(), creationDate(), icon(), encoding(), url(), passwordProtected(), username(), password(), autoUpdateType(), autoUpdateInitialInterval(), type(), &ok); if (!ok) { // Query failed. return false; } else { // New feed was added, fetch is primary id from the database. setId(new_id); setCustomId(QString::number(new_id)); return true; } } bool StandardFeed::editItself(StandardFeed* new_feed_data) { QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); StandardFeed* original_feed = this; RootItem* new_parent = new_feed_data->parent(); if (!DatabaseQueries::editFeed(database, new_parent->id(), original_feed->id(), new_feed_data->title(), new_feed_data->description(), new_feed_data->icon(), new_feed_data->encoding(), new_feed_data->url(), new_feed_data->passwordProtected(), new_feed_data->username(), new_feed_data->password(), new_feed_data->autoUpdateType(), new_feed_data->autoUpdateInitialInterval(), new_feed_data->type())) { // Persistent storage update failed, no way to continue now. return false; } // Setup new model data for the original item. original_feed->setTitle(new_feed_data->title()); original_feed->setDescription(new_feed_data->description()); original_feed->setIcon(new_feed_data->icon()); original_feed->setEncoding(new_feed_data->encoding()); original_feed->setDescription(new_feed_data->description()); original_feed->setUrl(new_feed_data->url()); original_feed->setPasswordProtected(new_feed_data->passwordProtected()); original_feed->setUsername(new_feed_data->username()); original_feed->setPassword(new_feed_data->password()); original_feed->setAutoUpdateType(new_feed_data->autoUpdateType()); original_feed->setAutoUpdateInitialInterval(new_feed_data->autoUpdateInitialInterval()); original_feed->setType(new_feed_data->type()); // Editing is done. return true; } StandardFeed::Type StandardFeed::type() const { return m_type; } void StandardFeed::setType(StandardFeed::Type type) { m_type = type; } bool StandardFeed::passwordProtected() const { return m_passwordProtected; } void StandardFeed::setPasswordProtected(bool passwordProtected) { m_passwordProtected = passwordProtected; } QString StandardFeed::username() const { return m_username; } void StandardFeed::setUsername(const QString& username) { m_username = username; } QString StandardFeed::password() const { return m_password; } void StandardFeed::setPassword(const QString& password) { m_password = password; } QString StandardFeed::encoding() const { return m_encoding; } void StandardFeed::setEncoding(const QString& encoding) { m_encoding = encoding; } QList StandardFeed::obtainNewMessages(bool* error_during_obtaining) { QByteArray feed_contents; int download_timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); m_networkError = NetworkFactory::performNetworkOperation(url(), download_timeout, QByteArray(), QString(), feed_contents, QNetworkAccessManager::GetOperation, !username().isEmpty(), username(), password(), true).first; if (m_networkError != QNetworkReply::NoError) { qWarning("Error during fetching of new messages for feed '%s' (id %d).", qPrintable(url()), id()); setStatus(NetworkError); *error_during_obtaining = true; return QList(); } else if (status() != NewMessages) { setStatus(Normal); *error_during_obtaining = false; } // Encode downloaded data for further parsing. QTextCodec* codec = QTextCodec::codecForName(encoding().toLocal8Bit()); QString formatted_feed_contents; if (codec == nullptr) { // 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 messages; switch (type()) { case StandardFeed::Rss0X: case StandardFeed::Rss2X: messages = RssParser(formatted_feed_contents).messages(); break; case StandardFeed::Rdf: messages = RdfParser().parseXmlData(formatted_feed_contents); break; case StandardFeed::Atom10: messages = AtomParser(formatted_feed_contents).messages(); default: break; } return messages; } QNetworkReply::NetworkError StandardFeed::networkError() const { return m_networkError; } StandardFeed::StandardFeed(const QSqlRecord& record) : Feed(record) { setEncoding(record.value(FDS_DB_ENCODING_INDEX).toString()); setPasswordProtected(record.value(FDS_DB_PROTECTED_INDEX).toBool()); setUsername(record.value(FDS_DB_USERNAME_INDEX).toString()); if (record.value(FDS_DB_PASSWORD_INDEX).toString().isEmpty()) { setPassword(record.value(FDS_DB_PASSWORD_INDEX).toString()); } else { setPassword(TextFactory::decrypt(record.value(FDS_DB_PASSWORD_INDEX).toString())); } setAutoUpdateType(static_cast(record.value(FDS_DB_UPDATE_TYPE_INDEX).toInt())); setAutoUpdateInitialInterval(record.value(FDS_DB_UPDATE_INTERVAL_INDEX).toInt()); m_networkError = QNetworkReply::NoError; }