qBittorrent/src/base/rss/rssfeed.cpp

471 lines
15 KiB
C++
Raw Normal View History

/*
2015-10-14 12:49:29 +03:00
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
*
* This program 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 2
* of the License, or (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
*/
2011-01-29 14:57:52 +03:00
#include <QDebug>
2015-10-14 12:31:44 +03:00
2015-09-25 11:10:05 +03:00
#include "base/preferences.h"
#include "base/qinisettings.h"
2015-10-14 12:49:29 +03:00
#include "base/logger.h"
#include "base/bittorrent/session.h"
#include "base/bittorrent/magneturi.h"
2015-09-25 11:10:05 +03:00
#include "base/utils/misc.h"
2015-10-14 12:49:29 +03:00
#include "base/utils/fs.h"
2015-09-25 11:10:05 +03:00
#include "base/net/downloadmanager.h"
#include "base/net/downloadhandler.h"
2015-10-14 12:31:44 +03:00
#include "rssdownloadrulelist.h"
#include "rssarticle.h"
#include "rssparser.h"
#include "rssfolder.h"
#include "rssmanager.h"
#include "rssfeed.h"
2015-10-15 19:33:27 +03:00
namespace Rss
{
2015-10-15 19:33:27 +03:00
bool articleDateRecentThan(const ArticlePtr &left, const ArticlePtr &right)
{
return left->date() > right->date();
}
}
2015-10-15 19:33:27 +03:00
using namespace Rss;
Feed::Feed(Manager *manager, Folder *parent, const QString &url)
2015-10-14 12:49:29 +03:00
: m_manager(manager)
, m_parent(parent)
, m_url (QUrl::fromEncoded(url.toUtf8()).toString())
, m_icon(":/icons/oxygen/application-rss+xml.png")
, m_unreadCount(0)
, m_dirty(false)
, m_inErrorState(false)
, m_loading(false)
2012-08-19 18:22:30 +04:00
{
2015-10-14 12:49:29 +03:00
qDebug() << Q_FUNC_INFO << m_url;
// Listen for new RSS downloads
connect(manager->rssParser(), SIGNAL(feedTitle(QString,QString)), SLOT(handleFeedTitle(QString,QString)));
connect(manager->rssParser(), SIGNAL(newArticle(QString,QVariantHash)), SLOT(handleNewArticle(QString,QVariantHash)));
connect(manager->rssParser(), SIGNAL(feedParsingFinished(QString,QString)), SLOT(handleFeedParsingFinished(QString,QString)));
2015-10-14 12:49:29 +03:00
// Download the RSS Feed icon
Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(iconUrl(), true);
connect(handler, SIGNAL(downloadFinished(QString, QString)), this, SLOT(handleFinishedDownload(QString, QString)));
connect(handler, SIGNAL(downloadFailed(QString, QString)), this, SLOT(handleDownloadFailure(QString, QString)));
m_iconUrl = handler->url();
2011-09-26 21:25:59 +04:00
2015-10-14 12:49:29 +03:00
// Load old RSS articles
loadItemsFromDisk();
}
2015-10-15 19:33:27 +03:00
Feed::~Feed()
2012-08-19 18:22:30 +04:00
{
2015-10-14 12:49:29 +03:00
if (!m_icon.startsWith(":/") && QFile::exists(m_icon))
Utils::Fs::forceRemove(m_icon);
}
2015-10-15 19:33:27 +03:00
Folder *Feed::parent() const
2015-10-14 12:49:29 +03:00
{
return m_parent;
}
2015-10-15 19:33:27 +03:00
void Feed::setParent(Folder *parent)
2015-10-14 12:49:29 +03:00
{
m_parent = parent;
}
2015-10-15 19:33:27 +03:00
void Feed::saveItemsToDisk()
2012-08-19 18:22:30 +04:00
{
2015-10-14 12:49:29 +03:00
qDebug() << Q_FUNC_INFO << m_url;
if (!m_dirty) return;
markAsDirty(false);
2015-10-14 12:49:29 +03:00
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
QVariantList oldItems;
2012-07-14 15:45:40 +04:00
2015-10-15 19:33:27 +03:00
ArticleHash::ConstIterator it = m_articles.begin();
ArticleHash::ConstIterator itend = m_articles.end();
2015-10-14 12:49:29 +03:00
for ( ; it != itend; ++it) {
oldItems << it.value()->toHash();
}
qDebug("Saving %d old items for feed %s", oldItems.size(), qPrintable(displayName()));
QHash<QString, QVariant> allOldItems = qBTRSS.value("old_items", QHash<QString, QVariant>()).toHash();
allOldItems[m_url] = oldItems;
qBTRSS.setValue("old_items", allOldItems);
2011-09-26 21:25:59 +04:00
}
2015-10-15 19:33:27 +03:00
void Feed::loadItemsFromDisk()
2012-08-19 18:22:30 +04:00
{
2015-10-14 12:49:29 +03:00
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
QHash<QString, QVariant> allOldItems = qBTRSS.value("old_items", QHash<QString, QVariant>()).toHash();
const QVariantList oldItems = allOldItems.value(m_url, QVariantList()).toList();
qDebug("Loading %d old items for feed %s", oldItems.size(), qPrintable(displayName()));
foreach (const QVariant &var_it, oldItems) {
QVariantHash item = var_it.toHash();
2015-10-15 19:33:27 +03:00
ArticlePtr rssItem = Article::fromHash(this, item);
2015-10-14 12:49:29 +03:00
if (rssItem)
addArticle(rssItem);
}
2015-10-14 12:49:29 +03:00
}
2015-10-15 19:33:27 +03:00
void Feed::addArticle(const ArticlePtr &article)
2015-10-14 12:49:29 +03:00
{
int maxArticles = Preferences::instance()->getRSSMaxArticlesPerFeed();
if (!m_articles.contains(article->guid())) {
markAsDirty();
// Update unreadCount
if (!article->isRead())
++m_unreadCount;
// Insert in hash table
m_articles[article->guid()] = article;
// Insertion sort
2015-10-15 19:33:27 +03:00
ArticleList::Iterator lowerBound = qLowerBound(m_articlesByDate.begin(), m_articlesByDate.end(), article, articleDateRecentThan);
2015-10-14 12:49:29 +03:00
m_articlesByDate.insert(lowerBound, article);
int lbIndex = m_articlesByDate.indexOf(article);
if (m_articlesByDate.size() > maxArticles) {
2015-10-15 19:33:27 +03:00
ArticlePtr oldestArticle = m_articlesByDate.takeLast();
2015-10-14 12:49:29 +03:00
m_articles.remove(oldestArticle->guid());
// Update unreadCount
if (!oldestArticle->isRead())
--m_unreadCount;
}
// Check if article was inserted at the end of the list and will break max_articles limit
if (Preferences::instance()->isRssDownloadingEnabled()) {
if ((lbIndex < maxArticles) && !article->isRead())
downloadArticleTorrentIfMatching(m_manager->downloadRules(), article);
}
}
2015-10-14 12:49:29 +03:00
else {
// m_articles.contains(article->guid())
// Try to download skipped articles
if (Preferences::instance()->isRssDownloadingEnabled()) {
2015-10-15 19:33:27 +03:00
ArticlePtr skipped = m_articles.value(article->guid(), ArticlePtr());
2015-10-14 12:49:29 +03:00
if (skipped) {
if (!skipped->isRead())
downloadArticleTorrentIfMatching(m_manager->downloadRules(), skipped);
}
}
}
2011-09-26 21:25:59 +04:00
}
2015-10-15 19:33:27 +03:00
bool Feed::refresh()
2012-08-19 18:22:30 +04:00
{
2015-10-14 12:49:29 +03:00
if (m_loading) {
qWarning() << Q_FUNC_INFO << "Feed" << displayName() << "is already being refreshed, ignoring request";
return false;
}
m_loading = true;
// Download the RSS again
Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(m_url, true);
connect(handler, SIGNAL(downloadFinished(QString, QString)), this, SLOT(handleFinishedDownload(QString, QString)));
connect(handler, SIGNAL(downloadFailed(QString, QString)), this, SLOT(handleDownloadFailure(QString, QString)));
m_url = handler->url(); // sync URL encoding
return true;
}
2015-10-15 19:33:27 +03:00
QString Feed::id() const
2015-10-14 12:49:29 +03:00
{
return m_url;
}
2015-10-15 19:33:27 +03:00
void Feed::removeAllSettings()
2012-08-19 18:22:30 +04:00
{
2015-10-14 12:49:29 +03:00
qDebug() << "Removing all settings / history for feed: " << m_url;
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
QVariantHash feedsWDownloader = qBTRSS.value("downloader_on", QVariantHash()).toHash();
if (feedsWDownloader.contains(m_url)) {
feedsWDownloader.remove(m_url);
qBTRSS.setValue("downloader_on", feedsWDownloader);
}
QVariantHash allFeedsFilters = qBTRSS.value("feed_filters", QVariantHash()).toHash();
if (allFeedsFilters.contains(m_url)) {
allFeedsFilters.remove(m_url);
qBTRSS.setValue("feed_filters", allFeedsFilters);
}
QVariantHash allOldItems = qBTRSS.value("old_items", QVariantHash()).toHash();
if (allOldItems.contains(m_url)) {
allOldItems.remove(m_url);
qBTRSS.setValue("old_items", allOldItems);
}
}
2015-10-15 19:33:27 +03:00
bool Feed::isLoading() const
2012-08-19 18:22:30 +04:00
{
2015-10-14 12:49:29 +03:00
return m_loading;
}
2015-10-15 19:33:27 +03:00
QString Feed::title() const
2012-08-19 18:22:30 +04:00
{
2015-10-14 12:49:29 +03:00
return m_title;
}
2015-10-15 19:33:27 +03:00
void Feed::rename(const QString &newName)
2012-08-19 18:22:30 +04:00
{
2015-10-14 12:49:29 +03:00
qDebug() << "Renaming stream to" << newName;
m_alias = newName;
}
// Return the alias if the stream has one, the url if it has no alias
2015-10-15 19:33:27 +03:00
QString Feed::displayName() const
2012-08-19 18:22:30 +04:00
{
2015-10-14 12:49:29 +03:00
if (!m_alias.isEmpty())
return m_alias;
if (!m_title.isEmpty())
return m_title;
return m_url;
}
2015-10-15 19:33:27 +03:00
QString Feed::url() const
2012-08-19 18:22:30 +04:00
{
2015-10-14 12:49:29 +03:00
return m_url;
}
2015-10-15 19:33:27 +03:00
QString Feed::iconPath() const
2012-08-19 16:48:28 +04:00
{
2015-10-14 12:49:29 +03:00
if (m_inErrorState)
return QLatin1String(":/icons/oxygen/unavailable.png");
2012-08-19 18:22:30 +04:00
2015-10-14 12:49:29 +03:00
return m_icon;
}
2015-10-15 19:33:27 +03:00
bool Feed::hasCustomIcon() const
2012-08-19 18:22:30 +04:00
{
2015-10-14 12:49:29 +03:00
return !m_icon.startsWith(":/");
}
2015-10-15 19:33:27 +03:00
void Feed::setIconPath(const QString &path)
2012-08-19 18:22:30 +04:00
{
2015-10-14 12:49:29 +03:00
if (!path.isEmpty() && QFile::exists(path))
m_icon = path;
}
2015-10-15 19:33:27 +03:00
ArticlePtr Feed::getItem(const QString &guid) const
2012-08-19 18:22:30 +04:00
{
2015-10-14 12:49:29 +03:00
return m_articles.value(guid);
}
2015-10-15 19:33:27 +03:00
uint Feed::count() const
2012-08-19 18:22:30 +04:00
{
2015-10-14 12:49:29 +03:00
return m_articles.size();
}
2015-10-15 19:33:27 +03:00
void Feed::markAsRead()
2012-08-19 18:22:30 +04:00
{
2015-10-15 19:33:27 +03:00
ArticleHash::ConstIterator it = m_articles.begin();
ArticleHash::ConstIterator itend = m_articles.end();
2015-10-14 12:49:29 +03:00
for ( ; it != itend; ++it) {
it.value()->markAsRead();
}
m_unreadCount = 0;
m_manager->forwardFeedInfosChanged(m_url, displayName(), 0);
}
2015-10-15 19:33:27 +03:00
void Feed::markAsDirty(bool dirty)
2013-01-28 14:53:04 +04:00
{
2015-10-14 12:49:29 +03:00
m_dirty = dirty;
2013-01-28 14:53:04 +04:00
}
2015-10-15 19:33:27 +03:00
uint Feed::unreadCount() const
{
2015-10-14 12:49:29 +03:00
return m_unreadCount;
}
2015-10-15 19:33:27 +03:00
ArticleList Feed::articleListByDateDesc() const
2012-08-19 18:22:30 +04:00
{
2015-10-14 12:49:29 +03:00
return m_articlesByDate;
}
2015-10-15 19:33:27 +03:00
const ArticleHash &Feed::articleHash() const
2015-10-14 12:49:29 +03:00
{
return m_articles;
}
2015-10-15 19:33:27 +03:00
ArticleList Feed::unreadArticleListByDateDesc() const
2012-08-19 18:22:30 +04:00
{
2015-10-15 19:33:27 +03:00
ArticleList unreadNews;
2012-07-14 15:45:40 +04:00
2015-10-15 19:33:27 +03:00
ArticleList::ConstIterator it = m_articlesByDate.begin();
ArticleList::ConstIterator itend = m_articlesByDate.end();
2015-10-14 12:49:29 +03:00
for ( ; it != itend; ++it) {
if (!(*it)->isRead())
unreadNews << *it;
}
return unreadNews;
}
2015-05-04 02:09:30 +03:00
// download the icon from the address
2015-10-15 19:33:27 +03:00
QString Feed::iconUrl() const
2012-08-19 18:22:30 +04:00
{
2015-10-14 12:49:29 +03:00
// XXX: This works for most sites but it is not perfect
return QString("http://") + QUrl(m_url).host() + QString("/favicon.ico");
}
// read and store the downloaded rss' informations
2015-10-15 19:33:27 +03:00
void Feed::handleFinishedDownload(const QString &url, const QString &filePath)
2012-08-19 18:22:30 +04:00
{
2015-10-14 12:49:29 +03:00
if (url == m_url) {
qDebug() << Q_FUNC_INFO << "Successfully downloaded RSS feed at" << url;
// Parse the download RSS
m_manager->rssParser()->parseRssFile(m_url, filePath);
}
else if (url == m_iconUrl) {
m_icon = filePath;
qDebug() << Q_FUNC_INFO << "icon path:" << m_icon;
m_manager->forwardFeedIconChanged(m_url, m_icon);
}
}
2015-10-15 19:33:27 +03:00
void Feed::handleDownloadFailure(const QString &url, const QString &error)
2012-08-19 18:22:30 +04:00
{
2015-10-14 12:49:29 +03:00
if (url != m_url) return;
2012-08-19 18:22:30 +04:00
2015-10-14 12:49:29 +03:00
m_inErrorState = true;
m_loading = false;
m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount);
qWarning() << "Failed to download RSS feed at" << url;
qWarning() << "Reason:" << error;
}
2015-10-15 19:33:27 +03:00
void Feed::handleFeedTitle(const QString &feedUrl, const QString &title)
{
2015-10-14 12:49:29 +03:00
if (feedUrl != m_url) return;
if (m_title == title) return;
2015-10-14 12:49:29 +03:00
m_title = title;
2015-10-14 12:49:29 +03:00
// Notify that we now have something better than a URL to display
if (m_alias.isEmpty())
m_manager->forwardFeedInfosChanged(feedUrl, title, m_unreadCount);
}
2015-10-15 19:33:27 +03:00
void Feed::downloadArticleTorrentIfMatching(DownloadRuleList *rules, const ArticlePtr &article)
{
2015-10-14 12:49:29 +03:00
Q_ASSERT(Preferences::instance()->isRssDownloadingEnabled());
2015-10-15 19:33:27 +03:00
DownloadRulePtr matchingRule = rules->findMatchingRule(m_url, article->title());
2015-10-14 12:49:29 +03:00
if (!matchingRule) return;
if (matchingRule->ignoreDays() > 0) {
QDateTime lastMatch = matchingRule->lastMatch();
if (lastMatch.isValid()) {
if (QDateTime::currentDateTime() < lastMatch.addDays(matchingRule->ignoreDays())) {
connect(article.data(), SIGNAL(articleWasRead()), SLOT(handleArticleStateChanged()), Qt::UniqueConnection);
article->markAsRead();
return;
}
}
}
2015-10-14 12:49:29 +03:00
matchingRule->setLastMatch(QDateTime::currentDateTime());
rules->saveRulesToStorage();
// Download the torrent
const QString &torrentUrl = article->torrentUrl();
if (torrentUrl.isEmpty()) {
Logger::instance()->addMessage(tr("Automatic download of '%1' from '%2' RSS feed failed because it doesn't contain a torrent or a magnet link...").arg(article->title()).arg(displayName()), Log::WARNING);
article->markAsRead();
return;
}
2015-10-14 12:49:29 +03:00
Logger::instance()->addMessage(tr("Automatically downloading '%1' torrent from '%2' RSS feed...").arg(article->title()).arg(displayName()));
connect(BitTorrent::Session::instance(), SIGNAL(downloadFromUrlFinished(QString)), article.data(), SLOT(handleTorrentDownloadSuccess(const QString &)), Qt::UniqueConnection);
if (BitTorrent::MagnetUri(torrent_url).isValid())
article->markAsRead();
else
connect(BitTorrent::Session::instance(), SIGNAL(downloadFromUrlFinished(QString)), article.data(), SLOT(handleTorrentDownloadSuccess(const QString&)), Qt::UniqueConnection);
BitTorrent::AddTorrentParams params;
params.savePath = matchingRule->savePath();
params.label = matchingRule->label();
2015-10-15 19:33:27 +03:00
if (matchingRule->addPaused() == DownloadRule::ALWAYS_PAUSED)
2015-10-14 12:49:29 +03:00
params.addPaused = TriStateBool::True;
2015-10-15 19:33:27 +03:00
else if (matchingRule->addPaused() == DownloadRule::NEVER_PAUSED)
2015-10-14 12:49:29 +03:00
params.addPaused = TriStateBool::False;
BitTorrent::Session::instance()->addTorrent(torrentUrl, params);
}
2015-10-15 19:33:27 +03:00
void Feed::recheckRssItemsForDownload()
{
2015-10-14 12:49:29 +03:00
Q_ASSERT(Preferences::instance()->isRssDownloadingEnabled());
2015-10-15 19:33:27 +03:00
DownloadRuleList *rules = m_manager->downloadRules();
foreach (const ArticlePtr &article, m_articlesByDate) {
2015-10-14 12:49:29 +03:00
if (!article->isRead())
downloadArticleTorrentIfMatching(rules, article);
}
}
2015-10-15 19:33:27 +03:00
void Feed::handleNewArticle(const QString &feedUrl, const QVariantHash &articleData)
{
2015-10-14 12:49:29 +03:00
if (feedUrl != m_url) return;
2015-10-15 19:33:27 +03:00
ArticlePtr article = Article::fromHash(this, articleData);
2015-10-14 12:49:29 +03:00
if (article.isNull()) {
qDebug() << "Article hash corrupted or guid is uncomputable; feed url: " << feedUrl;
return;
}
Q_ASSERT(article);
addArticle(article);
2015-10-14 12:49:29 +03:00
m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount);
// FIXME: We should forward the information here but this would seriously decrease
// performance with current design.
//m_manager->forwardFeedContentChanged(m_url);
}
2015-10-15 19:33:27 +03:00
void Feed::handleFeedParsingFinished(const QString &feedUrl, const QString &error)
{
2015-10-14 12:49:29 +03:00
if (feedUrl != m_url) return;
2015-10-14 12:49:29 +03:00
if (!error.isEmpty()) {
qWarning() << "Failed to parse RSS feed at" << feedUrl;
qWarning() << "Reason:" << error;
}
2015-10-14 12:49:29 +03:00
m_loading = false;
m_inErrorState = !error.isEmpty();
2015-10-14 12:49:29 +03:00
m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount);
// XXX: Would not be needed if we did this in handleNewArticle() instead
m_manager->forwardFeedContentChanged(m_url);
2015-10-14 12:49:29 +03:00
saveItemsToDisk();
}
2015-10-15 19:33:27 +03:00
void Feed::handleArticleStateChanged()
2015-04-19 18:17:47 +03:00
{
2015-10-14 12:49:29 +03:00
m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount);
}
2015-10-15 19:33:27 +03:00
void Feed::decrementUnreadCount()
{
2015-10-14 12:49:29 +03:00
--m_unreadCount;
}