Browse Source

Fix coding style (Issue #2192).

adaptive-webui-19844
Vladimir Golovnev (Glassez) 9 years ago committed by Vladimir Golovnev (qlassez)
parent
commit
67758cb092
  1. 133
      src/base/rss/rssarticle.cpp
  2. 71
      src/base/rss/rssarticle.h
  3. 365
      src/base/rss/rssdownloadrule.cpp
  4. 101
      src/base/rss/rssdownloadrule.h
  5. 187
      src/base/rss/rssdownloadrulelist.cpp
  6. 43
      src/base/rss/rssdownloadrulelist.h
  7. 542
      src/base/rss/rssfeed.cpp
  8. 114
      src/base/rss/rssfeed.h
  9. 20
      src/base/rss/rssfile.cpp
  10. 43
      src/base/rss/rssfile.h
  11. 349
      src/base/rss/rssfolder.cpp
  12. 73
      src/base/rss/rssfolder.h
  13. 171
      src/base/rss/rssmanager.cpp
  14. 48
      src/base/rss/rssmanager.h
  15. 834
      src/base/rss/rssparser.cpp
  16. 47
      src/base/rss/rssparser.h
  17. 4
      src/gui/rss/feedlistwidget.cpp

133
src/base/rss/rssarticle.cpp

@ -1,6 +1,7 @@
/* /*
* Bittorrent Client using Qt4 and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere * 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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -36,93 +37,107 @@
#include "rssarticle.h" #include "rssarticle.h"
// public constructor // public constructor
RssArticle::RssArticle(RssFeed* parent, const QString& guid): RssArticle::RssArticle(RssFeed *parent, const QString &guid)
m_parent(parent), m_guid(guid), m_read(false) {} : m_parent(parent)
, m_guid(guid)
, m_read(false)
{
}
bool RssArticle::hasAttachment() const { bool RssArticle::hasAttachment() const
return !m_torrentUrl.isEmpty(); {
return !m_torrentUrl.isEmpty();
} }
QVariantHash RssArticle::toHash() const { QVariantHash RssArticle::toHash() const
QVariantHash item; {
item["title"] = m_title; QVariantHash item;
item["id"] = m_guid; item["title"] = m_title;
item["torrent_url"] = m_torrentUrl; item["id"] = m_guid;
item["news_link"] = m_link; item["torrent_url"] = m_torrentUrl;
item["description"] = m_description; item["news_link"] = m_link;
item["date"] = m_date; item["description"] = m_description;
item["author"] = m_author; item["date"] = m_date;
item["read"] = m_read; item["author"] = m_author;
return item; item["read"] = m_read;
return item;
} }
RssArticlePtr hashToRssArticle(RssFeed* parent, const QVariantHash& h) { RssArticlePtr RssArticle::fromHash(RssFeed *parent, const QVariantHash &h)
const QString guid = h.value("id").toString(); {
if (guid.isEmpty()) const QString guid = h.value("id").toString();
return RssArticlePtr(); if (guid.isEmpty())
return RssArticlePtr();
RssArticlePtr art(new RssArticle(parent, guid));
art->m_title = h.value("title", "").toString(); RssArticlePtr art(new RssArticle(parent, guid));
art->m_torrentUrl = h.value("torrent_url", "").toString(); art->m_title = h.value("title", "").toString();
art->m_link = h.value("news_link", "").toString(); art->m_torrentUrl = h.value("torrent_url", "").toString();
art->m_description = h.value("description").toString(); art->m_link = h.value("news_link", "").toString();
art->m_date = h.value("date").toDateTime(); art->m_description = h.value("description").toString();
art->m_author = h.value("author").toString(); art->m_date = h.value("date").toDateTime();
art->m_read = h.value("read", false).toBool(); art->m_author = h.value("author").toString();
art->m_read = h.value("read", false).toBool();
return art;
return art;
} }
RssFeed* RssArticle::parent() const { RssFeed *RssArticle::parent() const
return m_parent; {
return m_parent;
} }
const QString& RssArticle::author() const { const QString &RssArticle::author() const
return m_author; {
return m_author;
} }
const QString& RssArticle::torrentUrl() const { const QString &RssArticle::torrentUrl() const
return m_torrentUrl; {
return m_torrentUrl;
} }
const QString& RssArticle::link() const { const QString &RssArticle::link() const
return m_link; {
return m_link;
} }
QString RssArticle::description() const QString RssArticle::description() const
{ {
return m_description.isNull() ? "" : m_description; return m_description.isNull() ? "" : m_description;
} }
const QDateTime& RssArticle::date() const { const QDateTime &RssArticle::date() const
return m_date; {
return m_date;
} }
bool RssArticle::isRead() const { bool RssArticle::isRead() const
return m_read; {
return m_read;
} }
void RssArticle::markAsRead() { void RssArticle::markAsRead()
if (m_read) {
return; if (m_read) return;
m_read = true; m_read = true;
m_parent->decrementUnreadCount(); m_parent->decrementUnreadCount();
m_parent->markAsDirty(); m_parent->markAsDirty();
emit articleWasRead(); emit articleWasRead();
} }
const QString& RssArticle::guid() const const QString &RssArticle::guid() const
{ {
return m_guid; return m_guid;
} }
const QString& RssArticle::title() const const QString &RssArticle::title() const
{ {
return m_title; return m_title;
} }
void RssArticle::handleTorrentDownloadSuccess(const QString &url) { void RssArticle::handleTorrentDownloadSuccess(const QString &url)
if (url == m_torrentUrl) {
markAsRead(); if (url == m_torrentUrl)
markAsRead();
} }

71
src/base/rss/rssarticle.h

@ -1,6 +1,7 @@
/* /*
* Bittorrent Client using Qt4 and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere * 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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -42,47 +43,47 @@ class RssArticle;
typedef QSharedPointer<RssArticle> RssArticlePtr; typedef QSharedPointer<RssArticle> RssArticlePtr;
// Item of a rss stream, single information // Item of a rss stream, single information
class RssArticle : public QObject { class RssArticle: public QObject
Q_OBJECT {
Q_OBJECT
public: public:
RssArticle(RssFeed* parent, const QString& guid); RssArticle(RssFeed *parent, const QString &guid);
// Accessors
bool hasAttachment() const; // Accessors
const QString& guid() const; bool hasAttachment() const;
RssFeed* parent() const; const QString &guid() const;
const QString& title() const; RssFeed *parent() const;
const QString& author() const; const QString &title() const;
const QString& torrentUrl() const; const QString &author() const;
const QString& link() const; const QString &torrentUrl() const;
QString description() const; const QString &link() const;
const QDateTime& date() const; QString description() const;
bool isRead() const; const QDateTime &date() const;
// Setters bool isRead() const;
void markAsRead(); // Setters
// Serialization void markAsRead();
QVariantHash toHash() const;
// Serialization
QVariantHash toHash() const;
static RssArticlePtr fromHash(RssFeed *parent, const QVariantHash &hash);
signals: signals:
void articleWasRead(); void articleWasRead();
public slots: public slots:
void handleTorrentDownloadSuccess(const QString& url); void handleTorrentDownloadSuccess(const QString &url);
friend RssArticlePtr hashToRssArticle(RssFeed* parent, const QVariantHash& hash);
private: private:
RssFeed* m_parent; RssFeed *m_parent;
QString m_guid; QString m_guid;
QString m_title; QString m_title;
QString m_torrentUrl; QString m_torrentUrl;
QString m_link; QString m_link;
QString m_description; QString m_description;
QDateTime m_date; QDateTime m_date;
QString m_author; QString m_author;
bool m_read; bool m_read;
}; };
RssArticlePtr hashToRssArticle(RssFeed* parent, const QVariantHash& hash);
#endif // RSSARTICLE_H #endif // RSSARTICLE_H

365
src/base/rss/rssdownloadrule.cpp

@ -1,5 +1,5 @@
/* /*
* Bittorrent Client using Qt4 and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2010 Christophe Dumez * Copyright (C) 2010 Christophe Dumez
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -38,163 +38,272 @@
#include "rssarticle.h" #include "rssarticle.h"
#include "rssdownloadrule.h" #include "rssdownloadrule.h"
RssDownloadRule::RssDownloadRule(): m_enabled(false), m_useRegex(false), m_apstate(USE_GLOBAL) RssDownloadRule::RssDownloadRule()
: m_enabled(false)
, m_useRegex(false)
, m_apstate(USE_GLOBAL)
{ {
} }
bool RssDownloadRule::matches(const QString &article_title) const bool RssDownloadRule::matches(const QString &articleTitle) const
{ {
foreach (const QString& token, m_mustContain) { foreach (const QString &token, m_mustContain) {
if (!token.isEmpty()) { if (!token.isEmpty()) {
QRegExp reg(token, Qt::CaseInsensitive, m_useRegex ? QRegExp::RegExp : QRegExp::Wildcard); QRegExp reg(token, Qt::CaseInsensitive, m_useRegex ? QRegExp::RegExp : QRegExp::Wildcard);
if (reg.indexIn(article_title) < 0) if (reg.indexIn(articleTitle) < 0)
return false; return false;
} }
}
qDebug("Checking not matching tokens");
// Checking not matching
foreach (const QString& token, m_mustNotContain) {
if (!token.isEmpty()) {
QRegExp reg(token, Qt::CaseInsensitive, m_useRegex ? QRegExp::RegExp : QRegExp::Wildcard);
if (reg.indexIn(article_title) > -1)
return false;
} }
} qDebug("Checking not matching tokens");
if (!m_episodeFilter.isEmpty()) { // Checking not matching
qDebug("Checking episode filter"); foreach (const QString &token, m_mustNotContain) {
QRegExp f("(^\\d{1,4})x(.*;$)"); if (!token.isEmpty()) {
int pos = f.indexIn(m_episodeFilter); QRegExp reg(token, Qt::CaseInsensitive, m_useRegex ? QRegExp::RegExp : QRegExp::Wildcard);
if (pos < 0) if (reg.indexIn(articleTitle) > -1)
return false; return false;
QString s = f.cap(1);
QStringList eps = f.cap(2).split(";");
QString expStr;
expStr += "s0?" + s + "[ -_\\.]?" + "e0?";
foreach (const QString& ep, eps) {
if (ep.isEmpty())
continue;
if (ep.indexOf('-') != -1) { // Range detected
QString partialPattern = "s0?" + s + "[ -_\\.]?" + "e(0?\\d{1,4})";
QRegExp reg(partialPattern, Qt::CaseInsensitive);
if (ep.endsWith('-')) { // Infinite range
int epOurs = ep.left(ep.size() - 1).toInt();
// Extract partial match from article and compare as digits
pos = reg.indexIn(article_title);
if (pos != -1) {
int epTheirs = reg.cap(1).toInt();
if (epTheirs >= epOurs)
return true;
}
} }
else { // Normal range }
QStringList range = ep.split('-'); if (!m_episodeFilter.isEmpty()) {
Q_ASSERT(range.size() == 2); qDebug("Checking episode filter");
if (range.first().toInt() > range.last().toInt()) QRegExp f("(^\\d{1,4})x(.*;$)");
continue; // Ignore this subrule completely int pos = f.indexIn(m_episodeFilter);
if (pos < 0)
int epOursFirst = range.first().toInt(); return false;
int epOursLast = range.last().toInt();
QString s = f.cap(1);
// Extract partial match from article and compare as digits QStringList eps = f.cap(2).split(";");
pos = reg.indexIn(article_title); QString expStr;
if (pos != -1) { expStr += "s0?" + s + "[ -_\\.]?" + "e0?";
int epTheirs = reg.cap(1).toInt();
if (epOursFirst <= epTheirs && epOursLast >= epTheirs) foreach (const QString &ep, eps) {
return true; if (ep.isEmpty())
} continue;
if (ep.indexOf('-') != -1) { // Range detected
QString partialPattern = "s0?" + s + "[ -_\\.]?" + "e(0?\\d{1,4})";
QRegExp reg(partialPattern, Qt::CaseInsensitive);
if (ep.endsWith('-')) { // Infinite range
int epOurs = ep.left(ep.size() - 1).toInt();
// Extract partial match from article and compare as digits
pos = reg.indexIn(articleTitle);
if (pos != -1) {
int epTheirs = reg.cap(1).toInt();
if (epTheirs >= epOurs)
return true;
}
}
else { // Normal range
QStringList range = ep.split('-');
Q_ASSERT(range.size() == 2);
if (range.first().toInt() > range.last().toInt())
continue; // Ignore this subrule completely
int epOursFirst = range.first().toInt();
int epOursLast = range.last().toInt();
// Extract partial match from article and compare as digits
pos = reg.indexIn(articleTitle);
if (pos != -1) {
int epTheirs = reg.cap(1).toInt();
if (epOursFirst <= epTheirs && epOursLast >= epTheirs)
return true;
}
}
}
else { // Single number
QRegExp reg(expStr + ep + "\\D", Qt::CaseInsensitive);
if (reg.indexIn(articleTitle) != -1)
return true;
}
} }
} return false;
else { // Single number
QRegExp reg(expStr + ep + "\\D", Qt::CaseInsensitive);
if (reg.indexIn(article_title) != -1)
return true;
}
} }
return false; return true;
}
return true;
} }
void RssDownloadRule::setMustContain(const QString &tokens) void RssDownloadRule::setMustContain(const QString &tokens)
{ {
if (m_useRegex) if (m_useRegex)
m_mustContain = QStringList() << tokens; m_mustContain = QStringList() << tokens;
else else
m_mustContain = tokens.split(" "); m_mustContain = tokens.split(" ");
} }
void RssDownloadRule::setMustNotContain(const QString &tokens) void RssDownloadRule::setMustNotContain(const QString &tokens)
{ {
if (m_useRegex) if (m_useRegex)
m_mustNotContain = QStringList() << tokens; m_mustNotContain = QStringList() << tokens;
else else
m_mustNotContain = tokens.split("|"); m_mustNotContain = tokens.split("|");
}
QStringList RssDownloadRule::rssFeeds() const
{
return m_rssFeeds;
} }
RssDownloadRulePtr RssDownloadRule::fromVariantHash(const QVariantHash &rule_hash) void RssDownloadRule::setRssFeeds(const QStringList &rssFeeds)
{ {
RssDownloadRulePtr rule(new RssDownloadRule); m_rssFeeds = rssFeeds;
rule->setName(rule_hash.value("name").toString()); }
rule->setUseRegex(rule_hash.value("use_regex", false).toBool());
rule->setMustContain(rule_hash.value("must_contain").toString()); QString RssDownloadRule::name() const
rule->setMustNotContain(rule_hash.value("must_not_contain").toString()); {
rule->setEpisodeFilter(rule_hash.value("episode_filter").toString()); return m_name;
rule->setRssFeeds(rule_hash.value("affected_feeds").toStringList()); }
rule->setEnabled(rule_hash.value("enabled", false).toBool());
rule->setSavePath(rule_hash.value("save_path").toString()); void RssDownloadRule::setName(const QString &name)
rule->setLabel(rule_hash.value("label_assigned").toString()); {
rule->setAddPaused(AddPausedState(rule_hash.value("add_paused").toUInt())); m_name = name;
rule->setLastMatch(rule_hash.value("last_match").toDateTime()); }
rule->setIgnoreDays(rule_hash.value("ignore_days").toInt());
return rule; QString RssDownloadRule::savePath() const
{
return m_savePath;
}
RssDownloadRulePtr RssDownloadRule::fromVariantHash(const QVariantHash &ruleHash)
{
RssDownloadRulePtr rule(new RssDownloadRule);
rule->setName(ruleHash.value("name").toString());
rule->setUseRegex(ruleHash.value("use_regex", false).toBool());
rule->setMustContain(ruleHash.value("must_contain").toString());
rule->setMustNotContain(ruleHash.value("must_not_contain").toString());
rule->setEpisodeFilter(ruleHash.value("episode_filter").toString());
rule->setRssFeeds(ruleHash.value("affected_feeds").toStringList());
rule->setEnabled(ruleHash.value("enabled", false).toBool());
rule->setSavePath(ruleHash.value("save_path").toString());
rule->setLabel(ruleHash.value("label_assigned").toString());
rule->setAddPaused(AddPausedState(ruleHash.value("add_paused").toUInt()));
rule->setLastMatch(ruleHash.value("last_match").toDateTime());
rule->setIgnoreDays(ruleHash.value("ignore_days").toInt());
return rule;
} }
QVariantHash RssDownloadRule::toVariantHash() const QVariantHash RssDownloadRule::toVariantHash() const
{ {
QVariantHash hash; QVariantHash hash;
hash["name"] = m_name; hash["name"] = m_name;
hash["must_contain"] = m_mustContain.join(" "); hash["must_contain"] = m_mustContain.join(" ");
hash["must_not_contain"] = m_mustNotContain.join("|"); hash["must_not_contain"] = m_mustNotContain.join("|");
hash["save_path"] = m_savePath; hash["save_path"] = m_savePath;
hash["affected_feeds"] = m_rssFeeds; hash["affected_feeds"] = m_rssFeeds;
hash["enabled"] = m_enabled; hash["enabled"] = m_enabled;
hash["label_assigned"] = m_label; hash["label_assigned"] = m_label;
hash["use_regex"] = m_useRegex; hash["use_regex"] = m_useRegex;
hash["add_paused"] = m_apstate; hash["add_paused"] = m_apstate;
hash["episode_filter"] = m_episodeFilter; hash["episode_filter"] = m_episodeFilter;
hash["last_match"] = m_lastMatch; hash["last_match"] = m_lastMatch;
hash["ignore_days"] = m_ignoreDays; hash["ignore_days"] = m_ignoreDays;
return hash; return hash;
}
bool RssDownloadRule::operator==(const RssDownloadRule &other) const
{
return m_name == other.name();
}
void RssDownloadRule::setSavePath(const QString &savePath)
{
if (!savePath.isEmpty() && (QDir(savePath) != QDir(Preferences::instance()->getSavePath())))
m_savePath = Utils::Fs::fromNativePath(savePath);
else
m_savePath = QString();
}
RssDownloadRule::AddPausedState RssDownloadRule::addPaused() const
{
return m_apstate;
}
void RssDownloadRule::setAddPaused(const RssDownloadRule::AddPausedState &aps)
{
m_apstate = aps;
}
QString RssDownloadRule::label() const
{
return m_label;
}
void RssDownloadRule::setLabel(const QString &label)
{
m_label = label;
}
bool RssDownloadRule::isEnabled() const
{
return m_enabled;
} }
bool RssDownloadRule::operator==(const RssDownloadRule &other) const { void RssDownloadRule::setEnabled(bool enable)
return m_name == other.name(); {
m_enabled = enable;
}
void RssDownloadRule::setLastMatch(const QDateTime &d)
{
m_lastMatch = d;
}
QDateTime RssDownloadRule::lastMatch() const
{
return m_lastMatch;
} }
void RssDownloadRule::setSavePath(const QString &save_path) void RssDownloadRule::setIgnoreDays(int d)
{ {
if (!save_path.isEmpty() && QDir(save_path) != QDir(Preferences::instance()->getSavePath())) m_ignoreDays = d;
m_savePath = Utils::Fs::fromNativePath(save_path);
else
m_savePath = QString();
} }
QStringList RssDownloadRule::findMatchingArticles(const RssFeedPtr& feed) const int RssDownloadRule::ignoreDays() const
{ {
QStringList ret; return m_ignoreDays;
const RssArticleHash& feed_articles = feed->articleHash(); }
RssArticleHash::ConstIterator artIt = feed_articles.begin(); QString RssDownloadRule::mustContain() const
RssArticleHash::ConstIterator artItend = feed_articles.end(); {
for ( ; artIt != artItend ; ++artIt) { return m_mustContain.join(" ");
const QString title = artIt.value()->title(); }
if (matches(title))
ret << title; QString RssDownloadRule::mustNotContain() const
} {
return ret; return m_mustNotContain.join("|");
}
bool RssDownloadRule::useRegex() const
{
return m_useRegex;
}
void RssDownloadRule::setUseRegex(bool enabled)
{
m_useRegex = enabled;
}
QString RssDownloadRule::episodeFilter() const
{
return m_episodeFilter;
}
void RssDownloadRule::setEpisodeFilter(const QString &e)
{
m_episodeFilter = e;
}
QStringList RssDownloadRule::findMatchingArticles(const RssFeedPtr &feed) const
{
QStringList ret;
const RssArticleHash &feedArticles = feed->articleHash();
RssArticleHash::ConstIterator artIt = feedArticles.begin();
RssArticleHash::ConstIterator artItend = feedArticles.end();
for ( ; artIt != artItend ; ++artIt) {
const QString title = artIt.value()->title();
if (matches(title))
ret << title;
}
return ret;
} }

101
src/base/rss/rssdownloadrule.h

@ -1,5 +1,5 @@
/* /*
* Bittorrent Client using Qt4 and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2010 Christophe Dumez * Copyright (C) 2010 Christophe Dumez
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -44,59 +44,60 @@ typedef QSharedPointer<RssDownloadRule> RssDownloadRulePtr;
class RssDownloadRule class RssDownloadRule
{ {
public: public:
enum AddPausedState { enum AddPausedState
USE_GLOBAL = 0, {
ALWAYS_PAUSED, USE_GLOBAL = 0,
NEVER_PAUSED ALWAYS_PAUSED,
}; NEVER_PAUSED
};
RssDownloadRule();
explicit RssDownloadRule(); static RssDownloadRulePtr fromVariantHash(const QVariantHash &ruleHash);
static RssDownloadRulePtr fromVariantHash(const QVariantHash &rule_hash); QVariantHash toVariantHash() const;
QVariantHash toVariantHash() const; bool matches(const QString &articleTitle) const;
bool matches(const QString &article_title) const; void setMustContain(const QString &tokens);
void setMustContain(const QString &tokens); void setMustNotContain(const QString &tokens);
void setMustNotContain(const QString &tokens); QStringList rssFeeds() const;
inline QStringList rssFeeds() const { return m_rssFeeds; } void setRssFeeds(const QStringList &rssFeeds);
inline void setRssFeeds(const QStringList& rss_feeds) { m_rssFeeds = rss_feeds; } QString name() const;
inline QString name() const { return m_name; } void setName(const QString &name);
inline void setName(const QString &name) { m_name = name; } QString savePath() const;
inline QString savePath() const { return m_savePath; } void setSavePath(const QString &savePath);
void setSavePath(const QString &save_path); AddPausedState addPaused() const;
inline AddPausedState addPaused() const { return m_apstate; } void setAddPaused(const AddPausedState &aps);
inline void setAddPaused(const AddPausedState &aps) { m_apstate = aps; } QString label() const;
inline QString label() const { return m_label; } void setLabel(const QString &label);
inline void setLabel(const QString &_label) { m_label = _label; } bool isEnabled() const;
inline bool isEnabled() const { return m_enabled; } void setEnabled(bool enable);
inline void setEnabled(bool enable) { m_enabled = enable; } void setLastMatch(const QDateTime &d);
inline void setLastMatch(const QDateTime& d) { m_lastMatch = d; } QDateTime lastMatch() const;
inline QDateTime lastMatch() const { return m_lastMatch; } void setIgnoreDays(int d);
inline void setIgnoreDays(int d) { m_ignoreDays = d; } int ignoreDays() const;
inline int ignoreDays() const { return m_ignoreDays; } QString mustContain() const;
inline QString mustContain() const { return m_mustContain.join(" "); } QString mustNotContain() const;
inline QString mustNotContain() const { return m_mustNotContain.join("|"); } bool useRegex() const;
inline bool useRegex() const { return m_useRegex; } void setUseRegex(bool enabled);
inline void setUseRegex(bool enabled) { m_useRegex = enabled; } QString episodeFilter() const;
inline QString episodeFilter() const { return m_episodeFilter; } void setEpisodeFilter(const QString &e);
inline void setEpisodeFilter(const QString& e) { m_episodeFilter = e; } QStringList findMatchingArticles(const RssFeedPtr &feed) const;
QStringList findMatchingArticles(const RssFeedPtr& feed) const; // Operators
// Operators bool operator==(const RssDownloadRule &other) const;
bool operator==(const RssDownloadRule &other) const;
private: private:
QString m_name; QString m_name;
QStringList m_mustContain; QStringList m_mustContain;
QStringList m_mustNotContain; QStringList m_mustNotContain;
QString m_episodeFilter; QString m_episodeFilter;
QString m_savePath; QString m_savePath;
QString m_label; QString m_label;
bool m_enabled; bool m_enabled;
QStringList m_rssFeeds; QStringList m_rssFeeds;
bool m_useRegex; bool m_useRegex;
AddPausedState m_apstate; AddPausedState m_apstate;
QDateTime m_lastMatch; QDateTime m_lastMatch;
int m_ignoreDays; int m_ignoreDays;
}; };
#endif // RSSDOWNLOADRULE_H #endif // RSSDOWNLOADRULE_H

187
src/base/rss/rssdownloadrulelist.cpp

@ -1,5 +1,5 @@
/* /*
* Bittorrent Client using Qt4 and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2010 Christophe Dumez * Copyright (C) 2010 Christophe Dumez
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -38,135 +38,146 @@
RssDownloadRuleList::RssDownloadRuleList() RssDownloadRuleList::RssDownloadRuleList()
{ {
loadRulesFromStorage(); loadRulesFromStorage();
} }
RssDownloadRulePtr RssDownloadRuleList::findMatchingRule(const QString &feed_url, const QString &article_title) const RssDownloadRulePtr RssDownloadRuleList::findMatchingRule(const QString &feedUrl, const QString &articleTitle) const
{ {
Q_ASSERT(Preferences::instance()->isRssDownloadingEnabled()); Q_ASSERT(Preferences::instance()->isRssDownloadingEnabled());
QStringList rule_names = m_feedRules.value(feed_url); QStringList ruleNames = m_feedRules.value(feedUrl);
foreach (const QString &rule_name, rule_names) { foreach (const QString &rule_name, ruleNames) {
RssDownloadRulePtr rule = m_rules[rule_name]; RssDownloadRulePtr rule = m_rules[rule_name];
if (rule->isEnabled() && rule->matches(article_title)) return rule; if (rule->isEnabled() && rule->matches(articleTitle)) return rule;
} }
return RssDownloadRulePtr(); return RssDownloadRulePtr();
} }
void RssDownloadRuleList::replace(RssDownloadRuleList *other) { void RssDownloadRuleList::replace(RssDownloadRuleList *other)
m_rules.clear(); {
m_feedRules.clear(); m_rules.clear();
foreach (const QString& name, other->ruleNames()) { m_feedRules.clear();
saveRule(other->getRule(name)); foreach (const QString &name, other->ruleNames()) {
} saveRule(other->getRule(name));
}
} }
void RssDownloadRuleList::saveRulesToStorage() void RssDownloadRuleList::saveRulesToStorage()
{ {
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss"); QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
qBTRSS.setValue("download_rules", toVariantHash()); qBTRSS.setValue("download_rules", toVariantHash());
} }
void RssDownloadRuleList::loadRulesFromStorage() void RssDownloadRuleList::loadRulesFromStorage()
{ {
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss"); QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
loadRulesFromVariantHash(qBTRSS.value("download_rules").toHash()); loadRulesFromVariantHash(qBTRSS.value("download_rules").toHash());
} }
QVariantHash RssDownloadRuleList::toVariantHash() const QVariantHash RssDownloadRuleList::toVariantHash() const
{ {
QVariantHash ret; QVariantHash ret;
foreach (const RssDownloadRulePtr &rule, m_rules.values()) { foreach (const RssDownloadRulePtr &rule, m_rules.values()) {
ret.insert(rule->name(), rule->toVariantHash()); ret.insert(rule->name(), rule->toVariantHash());
} }
return ret; return ret;
} }
void RssDownloadRuleList::loadRulesFromVariantHash(const QVariantHash &h) void RssDownloadRuleList::loadRulesFromVariantHash(const QVariantHash &h)
{ {
QVariantHash::ConstIterator it = h.begin(); QVariantHash::ConstIterator it = h.begin();
QVariantHash::ConstIterator itend = h.end(); QVariantHash::ConstIterator itend = h.end();
for ( ; it != itend; ++it) { for ( ; it != itend; ++it) {
RssDownloadRulePtr rule = RssDownloadRule::fromVariantHash(it.value().toHash()); RssDownloadRulePtr rule = RssDownloadRule::fromVariantHash(it.value().toHash());
if (rule && !rule->name().isEmpty()) if (rule && !rule->name().isEmpty())
saveRule(rule); saveRule(rule);
} }
} }
void RssDownloadRuleList::saveRule(const RssDownloadRulePtr &rule) void RssDownloadRuleList::saveRule(const RssDownloadRulePtr &rule)
{ {
qDebug() << Q_FUNC_INFO << rule->name(); qDebug() << Q_FUNC_INFO << rule->name();
Q_ASSERT(rule); Q_ASSERT(rule);
if (m_rules.contains(rule->name())) { if (m_rules.contains(rule->name())) {
qDebug("This is an update, removing old rule first"); qDebug("This is an update, removing old rule first");
removeRule(rule->name()); removeRule(rule->name());
} }
m_rules.insert(rule->name(), rule); m_rules.insert(rule->name(), rule);
// Update feedRules hashtable // Update feedRules hashtable
foreach (const QString &feed_url, rule->rssFeeds()) { foreach (const QString &feedUrl, rule->rssFeeds()) {
m_feedRules[feed_url].append(rule->name()); m_feedRules[feedUrl].append(rule->name());
} }
qDebug() << Q_FUNC_INFO << "EXIT"; qDebug() << Q_FUNC_INFO << "EXIT";
} }
void RssDownloadRuleList::removeRule(const QString &name) void RssDownloadRuleList::removeRule(const QString &name)
{ {
qDebug() << Q_FUNC_INFO << name; qDebug() << Q_FUNC_INFO << name;
if (!m_rules.contains(name)) return; if (!m_rules.contains(name)) return;
RssDownloadRulePtr rule = m_rules.take(name); RssDownloadRulePtr rule = m_rules.take(name);
// Update feedRules hashtable // Update feedRules hashtable
foreach (const QString &feed_url, rule->rssFeeds()) { foreach (const QString &feedUrl, rule->rssFeeds()) {
m_feedRules[feed_url].removeOne(rule->name()); m_feedRules[feedUrl].removeOne(rule->name());
} }
} }
void RssDownloadRuleList::renameRule(const QString &old_name, const QString &new_name) void RssDownloadRuleList::renameRule(const QString &oldName, const QString &newName)
{ {
if (!m_rules.contains(old_name)) return; if (!m_rules.contains(oldName)) return;
RssDownloadRulePtr rule = m_rules.take(old_name);
rule->setName(new_name); RssDownloadRulePtr rule = m_rules.take(oldName);
m_rules.insert(new_name, rule); rule->setName(newName);
// Update feedRules hashtable m_rules.insert(newName, rule);
foreach (const QString &feed_url, rule->rssFeeds()) { // Update feedRules hashtable
m_feedRules[feed_url].replace(m_feedRules[feed_url].indexOf(old_name), new_name); foreach (const QString &feedUrl, rule->rssFeeds()) {
} m_feedRules[feedUrl].replace(m_feedRules[feedUrl].indexOf(oldName), newName);
}
} }
RssDownloadRulePtr RssDownloadRuleList::getRule(const QString &name) const RssDownloadRulePtr RssDownloadRuleList::getRule(const QString &name) const
{ {
return m_rules.value(name); return m_rules.value(name);
} }
bool RssDownloadRuleList::serialize(const QString& path) QStringList RssDownloadRuleList::ruleNames() const
{ {
QFile f(path); return m_rules.keys();
if (f.open(QIODevice::WriteOnly)) {
QDataStream out(&f);
out.setVersion(QDataStream::Qt_4_5);
out << toVariantHash();
f.close();
return true;
} else {
return false;
}
} }
bool RssDownloadRuleList::unserialize(const QString &path) bool RssDownloadRuleList::isEmpty() const
{
return m_rules.isEmpty();
}
bool RssDownloadRuleList::serialize(const QString &path)
{ {
QFile f(path); QFile f(path);
if (f.open(QIODevice::ReadOnly)) { if (f.open(QIODevice::WriteOnly)) {
QDataStream in(&f); QDataStream out(&f);
in.setVersion(QDataStream::Qt_4_5); out.setVersion(QDataStream::Qt_4_5);
QVariantHash tmp; out << toVariantHash();
in >> tmp; f.close();
f.close(); return true;
if (tmp.isEmpty()) }
return false;
qDebug("Processing was successful!");
loadRulesFromVariantHash(tmp);
return true;
} else {
qDebug("Error: could not open file at %s", qPrintable(path));
return false; return false;
}
} }
bool RssDownloadRuleList::unserialize(const QString &path)
{
QFile f(path);
if (f.open(QIODevice::ReadOnly)) {
QDataStream in(&f);
in.setVersion(QDataStream::Qt_4_5);
QVariantHash tmp;
in >> tmp;
f.close();
if (tmp.isEmpty())
return false;
qDebug("Processing was successful!");
loadRulesFromVariantHash(tmp);
return true;
} else {
qDebug("Error: could not open file at %s", qPrintable(path));
return false;
}
}

43
src/base/rss/rssdownloadrulelist.h

@ -1,5 +1,5 @@
/* /*
* Bittorrent Client using Qt4 and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2010 Christophe Dumez * Copyright (C) 2010 Christophe Dumez
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -34,36 +34,37 @@
#include <QList> #include <QList>
#include <QHash> #include <QHash>
#include <QVariantHash> #include <QVariantHash>
#include "rssdownloadrule.h" #include "rssdownloadrule.h"
class RssDownloadRuleList class RssDownloadRuleList
{ {
Q_DISABLE_COPY(RssDownloadRuleList) Q_DISABLE_COPY(RssDownloadRuleList)
public: public:
RssDownloadRuleList(); RssDownloadRuleList();
RssDownloadRulePtr findMatchingRule(const QString &feed_url, const QString &article_title) const;
// Operators
void saveRule(const RssDownloadRulePtr &rule);
void removeRule(const QString &name);
void renameRule(const QString &old_name, const QString &new_name);
RssDownloadRulePtr getRule(const QString &name) const;
inline QStringList ruleNames() const { return m_rules.keys(); }
inline bool isEmpty() const { return m_rules.isEmpty(); }
void saveRulesToStorage();
bool serialize(const QString& path);
bool unserialize(const QString& path);
void replace(RssDownloadRuleList* other);
private: RssDownloadRulePtr findMatchingRule(const QString &feedUrl, const QString &articleTitle) const;
void loadRulesFromStorage(); // Operators
void loadRulesFromVariantHash(const QVariantHash& l); void saveRule(const RssDownloadRulePtr &rule);
QVariantHash toVariantHash() const; void removeRule(const QString &name);
void renameRule(const QString &oldName, const QString &newName);
RssDownloadRulePtr getRule(const QString &name) const;
QStringList ruleNames() const;
bool isEmpty() const;
void saveRulesToStorage();
bool serialize(const QString &path);
bool unserialize(const QString &path);
void replace(RssDownloadRuleList *other);
private: private:
QHash<QString, RssDownloadRulePtr> m_rules; void loadRulesFromStorage();
QHash<QString, QStringList> m_feedRules; void loadRulesFromVariantHash(const QVariantHash &l);
QVariantHash toVariantHash() const;
private:
QHash<QString, RssDownloadRulePtr> m_rules;
QHash<QString, QStringList> m_feedRules;
}; };
#endif // RSSDOWNLOADFILTERLIST_H #endif // RSSDOWNLOADFILTERLIST_H

542
src/base/rss/rssfeed.cpp

@ -1,6 +1,7 @@
/* /*
* Bittorrent Client using Qt4 and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere * 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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -30,15 +31,15 @@
#include <QDebug> #include <QDebug>
#include "base/bittorrent/session.h"
#include "base/bittorrent/magneturi.h"
#include "base/preferences.h" #include "base/preferences.h"
#include "base/qinisettings.h" #include "base/qinisettings.h"
#include "base/logger.h"
#include "base/bittorrent/session.h"
#include "base/bittorrent/magneturi.h"
#include "base/utils/misc.h" #include "base/utils/misc.h"
#include "base/utils/fs.h"
#include "base/net/downloadmanager.h" #include "base/net/downloadmanager.h"
#include "base/net/downloadhandler.h" #include "base/net/downloadhandler.h"
#include "base/utils/fs.h"
#include "base/logger.h"
#include "rssdownloadrulelist.h" #include "rssdownloadrulelist.h"
#include "rssarticle.h" #include "rssarticle.h"
#include "rssparser.h" #include "rssparser.h"
@ -46,404 +47,419 @@
#include "rssmanager.h" #include "rssmanager.h"
#include "rssfeed.h" #include "rssfeed.h"
bool rssArticleDateRecentThan(const RssArticlePtr& left, const RssArticlePtr& right) bool rssArticleDateRecentThan(const RssArticlePtr &left, const RssArticlePtr &right)
{ {
return left->date() > right->date(); return left->date() > right->date();
} }
RssFeed::RssFeed(RssManager* manager, RssFolder* parent, const QString& url): RssFeed::RssFeed(RssManager *manager, RssFolder *parent, const QString &url)
m_manager(manager), : m_manager(manager)
m_parent(parent), , m_parent(parent)
m_url (QUrl::fromEncoded(url.toUtf8()).toString()), , m_url (QUrl::fromEncoded(url.toUtf8()).toString())
m_icon(":/icons/oxygen/application-rss+xml.png"), , m_icon(":/icons/oxygen/application-rss+xml.png")
m_unreadCount(0), , m_unreadCount(0)
m_dirty(false), , m_dirty(false)
m_inErrorState(false), , m_inErrorState(false)
m_loading(false) , m_loading(false)
{ {
qDebug() << Q_FUNC_INFO << m_url; qDebug() << Q_FUNC_INFO << m_url;
// Listen for new RSS downloads // Listen for new RSS downloads
connect(manager->rssParser(), SIGNAL(feedTitle(QString,QString)), SLOT(handleFeedTitle(QString,QString))); 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(newArticle(QString,QVariantHash)), SLOT(handleNewArticle(QString,QVariantHash)));
connect(manager->rssParser(), SIGNAL(feedParsingFinished(QString,QString)), SLOT(handleFeedParsingFinished(QString,QString))); connect(manager->rssParser(), SIGNAL(feedParsingFinished(QString,QString)), SLOT(handleFeedParsingFinished(QString,QString)));
// Download the RSS Feed icon // Download the RSS Feed icon
Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(iconUrl(), true); Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(iconUrl(), true);
connect(handler, SIGNAL(downloadFinished(QString, QString)), this, SLOT(handleFinishedDownload(QString, QString))); connect(handler, SIGNAL(downloadFinished(QString, QString)), this, SLOT(handleFinishedDownload(QString, QString)));
connect(handler, SIGNAL(downloadFailed(QString, QString)), this, SLOT(handleDownloadFailure(QString, QString))); connect(handler, SIGNAL(downloadFailed(QString, QString)), this, SLOT(handleDownloadFailure(QString, QString)));
m_iconUrl = handler->url(); m_iconUrl = handler->url();
// Load old RSS articles // Load old RSS articles
loadItemsFromDisk(); loadItemsFromDisk();
} }
RssFeed::~RssFeed() RssFeed::~RssFeed()
{ {
if (!m_icon.startsWith(":/") && QFile::exists(m_icon)) if (!m_icon.startsWith(":/") && QFile::exists(m_icon))
Utils::Fs::forceRemove(m_icon); Utils::Fs::forceRemove(m_icon);
}
RssFolder *RssFeed::parent() const
{
return m_parent;
}
void RssFeed::setParent(RssFolder *parent)
{
m_parent = parent;
} }
void RssFeed::saveItemsToDisk() void RssFeed::saveItemsToDisk()
{ {
qDebug() << Q_FUNC_INFO << m_url; qDebug() << Q_FUNC_INFO << m_url;
if (!m_dirty) if (!m_dirty) return;
return;
markAsDirty(false); markAsDirty(false);
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss"); QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
QVariantList old_items; QVariantList oldItems;
RssArticleHash::ConstIterator it = m_articles.begin(); RssArticleHash::ConstIterator it = m_articles.begin();
RssArticleHash::ConstIterator itend = m_articles.end(); RssArticleHash::ConstIterator itend = m_articles.end();
for ( ; it != itend; ++it) { for ( ; it != itend; ++it) {
old_items << it.value()->toHash(); oldItems << it.value()->toHash();
} }
qDebug("Saving %d old items for feed %s", old_items.size(), qPrintable(displayName())); qDebug("Saving %d old items for feed %s", oldItems.size(), qPrintable(displayName()));
QHash<QString, QVariant> all_old_items = qBTRSS.value("old_items", QHash<QString, QVariant>()).toHash(); QHash<QString, QVariant> allOldItems = qBTRSS.value("old_items", QHash<QString, QVariant>()).toHash();
all_old_items[m_url] = old_items; allOldItems[m_url] = oldItems;
qBTRSS.setValue("old_items", all_old_items); qBTRSS.setValue("old_items", allOldItems);
} }
void RssFeed::loadItemsFromDisk() void RssFeed::loadItemsFromDisk()
{ {
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss"); QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
QHash<QString, QVariant> all_old_items = qBTRSS.value("old_items", QHash<QString, QVariant>()).toHash(); QHash<QString, QVariant> allOldItems = qBTRSS.value("old_items", QHash<QString, QVariant>()).toHash();
const QVariantList old_items = all_old_items.value(m_url, QVariantList()).toList(); const QVariantList oldItems = allOldItems.value(m_url, QVariantList()).toList();
qDebug("Loading %d old items for feed %s", old_items.size(), qPrintable(displayName())); qDebug("Loading %d old items for feed %s", oldItems.size(), qPrintable(displayName()));
foreach (const QVariant& var_it, old_items) { foreach (const QVariant &var_it, oldItems) {
QVariantHash item = var_it.toHash(); QVariantHash item = var_it.toHash();
RssArticlePtr rss_item = hashToRssArticle(this, item); RssArticlePtr rssItem = RssArticle::fromHash(this, item);
if (rss_item) if (rssItem)
addArticle(rss_item); addArticle(rssItem);
}
}
void RssFeed::addArticle(const RssArticlePtr& article) {
int max_articles = 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
RssArticleList::Iterator lowerBound = qLowerBound(m_articlesByDate.begin(), m_articlesByDate.end(), article, rssArticleDateRecentThan);
m_articlesByDate.insert(lowerBound, article);
int lbIndex = m_articlesByDate.indexOf(article);
if (m_articlesByDate.size() > max_articles) {
RssArticlePtr oldestArticle = m_articlesByDate.takeLast();
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 void RssFeed::addArticle(const RssArticlePtr &article)
if (Preferences::instance()->isRssDownloadingEnabled()) { {
if (lbIndex < max_articles && !article->isRead()) int maxArticles = Preferences::instance()->getRSSMaxArticlesPerFeed();
downloadArticleTorrentIfMatching(m_manager->downloadRules(), article);
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
RssArticleList::Iterator lowerBound = qLowerBound(m_articlesByDate.begin(), m_articlesByDate.end(), article, rssArticleDateRecentThan);
m_articlesByDate.insert(lowerBound, article);
int lbIndex = m_articlesByDate.indexOf(article);
if (m_articlesByDate.size() > maxArticles) {
RssArticlePtr oldestArticle = m_articlesByDate.takeLast();
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);
}
} }
} else {
else { // m_articles.contains(article->guid())
// m_articles.contains(article->guid()) // Try to download skipped articles
// Try to download skipped articles if (Preferences::instance()->isRssDownloadingEnabled()) {
if (Preferences::instance()->isRssDownloadingEnabled()) { RssArticlePtr skipped = m_articles.value(article->guid(), RssArticlePtr());
RssArticlePtr skipped = m_articles.value(article->guid(), RssArticlePtr()); if (skipped) {
if (skipped) { if (!skipped->isRead())
if (!skipped->isRead()) downloadArticleTorrentIfMatching(m_manager->downloadRules(), skipped);
downloadArticleTorrentIfMatching(m_manager->downloadRules(), skipped); }
} }
} }
}
} }
bool RssFeed::refresh() bool RssFeed::refresh()
{ {
if (m_loading) { if (m_loading) {
qWarning() << Q_FUNC_INFO << "Feed" << displayName() << "is already being refreshed, ignoring request"; qWarning() << Q_FUNC_INFO << "Feed" << displayName() << "is already being refreshed, ignoring request";
return false; return false;
} }
m_loading = true; m_loading = true;
// Download the RSS again // Download the RSS again
Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(m_url, true); Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(m_url, true);
connect(handler, SIGNAL(downloadFinished(QString, QString)), this, SLOT(handleFinishedDownload(QString, QString))); connect(handler, SIGNAL(downloadFinished(QString, QString)), this, SLOT(handleFinishedDownload(QString, QString)));
connect(handler, SIGNAL(downloadFailed(QString, QString)), this, SLOT(handleDownloadFailure(QString, QString))); connect(handler, SIGNAL(downloadFailed(QString, QString)), this, SLOT(handleDownloadFailure(QString, QString)));
m_url = handler->url(); // sync URL encoding m_url = handler->url(); // sync URL encoding
return true; return true;
}
QString RssFeed::id() const
{
return m_url;
} }
void RssFeed::removeAllSettings() void RssFeed::removeAllSettings()
{ {
qDebug() << "Removing all settings / history for feed: " << m_url; qDebug() << "Removing all settings / history for feed: " << m_url;
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss"); QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
QVariantHash feeds_w_downloader = qBTRSS.value("downloader_on", QVariantHash()).toHash(); QVariantHash feedsWDownloader = qBTRSS.value("downloader_on", QVariantHash()).toHash();
if (feeds_w_downloader.contains(m_url)) { if (feedsWDownloader.contains(m_url)) {
feeds_w_downloader.remove(m_url); feedsWDownloader.remove(m_url);
qBTRSS.setValue("downloader_on", feeds_w_downloader); qBTRSS.setValue("downloader_on", feedsWDownloader);
} }
QVariantHash all_feeds_filters = qBTRSS.value("feed_filters", QVariantHash()).toHash(); QVariantHash allFeedsFilters = qBTRSS.value("feed_filters", QVariantHash()).toHash();
if (all_feeds_filters.contains(m_url)) { if (allFeedsFilters.contains(m_url)) {
all_feeds_filters.remove(m_url); allFeedsFilters.remove(m_url);
qBTRSS.setValue("feed_filters", all_feeds_filters); qBTRSS.setValue("feed_filters", allFeedsFilters);
} }
QVariantHash all_old_items = qBTRSS.value("old_items", QVariantHash()).toHash(); QVariantHash allOldItems = qBTRSS.value("old_items", QVariantHash()).toHash();
if (all_old_items.contains(m_url)) { if (allOldItems.contains(m_url)) {
all_old_items.remove(m_url); allOldItems.remove(m_url);
qBTRSS.setValue("old_items", all_old_items); qBTRSS.setValue("old_items", allOldItems);
} }
} }
bool RssFeed::isLoading() const bool RssFeed::isLoading() const
{ {
return m_loading; return m_loading;
} }
QString RssFeed::title() const QString RssFeed::title() const
{ {
return m_title; return m_title;
} }
void RssFeed::rename(const QString &new_name) void RssFeed::rename(const QString &newName)
{ {
qDebug() << "Renaming stream to" << new_name; qDebug() << "Renaming stream to" << newName;
m_alias = new_name; m_alias = newName;
} }
// Return the alias if the stream has one, the url if it has no alias // Return the alias if the stream has one, the url if it has no alias
QString RssFeed::displayName() const QString RssFeed::displayName() const
{ {
if (!m_alias.isEmpty()) if (!m_alias.isEmpty())
return m_alias; return m_alias;
if (!m_title.isEmpty()) if (!m_title.isEmpty())
return m_title; return m_title;
return m_url; return m_url;
} }
QString RssFeed::url() const QString RssFeed::url() const
{ {
return m_url; return m_url;
} }
QString RssFeed::iconPath() const QString RssFeed::iconPath() const
{ {
if (m_inErrorState) if (m_inErrorState)
return QLatin1String(":/icons/oxygen/unavailable.png"); return QLatin1String(":/icons/oxygen/unavailable.png");
return m_icon; return m_icon;
} }
bool RssFeed::hasCustomIcon() const bool RssFeed::hasCustomIcon() const
{ {
return !m_icon.startsWith(":/"); return !m_icon.startsWith(":/");
} }
void RssFeed::setIconPath(const QString& path) void RssFeed::setIconPath(const QString &path)
{ {
if (path.isEmpty() || !QFile::exists(path)) if (!path.isEmpty() && QFile::exists(path))
return; m_icon = path;
m_icon = path;
} }
RssArticlePtr RssFeed::getItem(const QString& guid) const RssArticlePtr RssFeed::getItem(const QString &guid) const
{ {
return m_articles.value(guid); return m_articles.value(guid);
} }
uint RssFeed::count() const uint RssFeed::count() const
{ {
return m_articles.size(); return m_articles.size();
} }
void RssFeed::markAsRead() void RssFeed::markAsRead()
{ {
RssArticleHash::ConstIterator it = m_articles.begin(); RssArticleHash::ConstIterator it = m_articles.begin();
RssArticleHash::ConstIterator itend = m_articles.end(); RssArticleHash::ConstIterator itend = m_articles.end();
for ( ; it != itend; ++it) { for ( ; it != itend; ++it) {
it.value()->markAsRead(); it.value()->markAsRead();
} }
m_unreadCount = 0; m_unreadCount = 0;
m_manager->forwardFeedInfosChanged(m_url, displayName(), 0); m_manager->forwardFeedInfosChanged(m_url, displayName(), 0);
} }
void RssFeed::markAsDirty(bool dirty) void RssFeed::markAsDirty(bool dirty)
{ {
m_dirty = dirty; m_dirty = dirty;
} }
uint RssFeed::unreadCount() const uint RssFeed::unreadCount() const
{ {
return m_unreadCount; return m_unreadCount;
} }
RssArticleList RssFeed::articleListByDateDesc() const RssArticleList RssFeed::articleListByDateDesc() const
{ {
return m_articlesByDate; return m_articlesByDate;
}
const RssArticleHash &RssFeed::articleHash() const
{
return m_articles;
} }
RssArticleList RssFeed::unreadArticleListByDateDesc() const RssArticleList RssFeed::unreadArticleListByDateDesc() const
{ {
RssArticleList unread_news; RssArticleList unreadNews;
RssArticleList::ConstIterator it = m_articlesByDate.begin(); RssArticleList::ConstIterator it = m_articlesByDate.begin();
RssArticleList::ConstIterator itend = m_articlesByDate.end(); RssArticleList::ConstIterator itend = m_articlesByDate.end();
for ( ; it != itend; ++it) { for ( ; it != itend; ++it) {
if (!(*it)->isRead()) if (!(*it)->isRead())
unread_news << *it; unreadNews << *it;
} }
return unread_news; return unreadNews;
} }
// download the icon from the address // download the icon from the address
QString RssFeed::iconUrl() const QString RssFeed::iconUrl() const
{ {
// XXX: This works for most sites but it is not perfect // XXX: This works for most sites but it is not perfect
return QString("http://") + QUrl(m_url).host() + QString("/favicon.ico"); return QString("http://") + QUrl(m_url).host() + QString("/favicon.ico");
} }
// read and store the downloaded rss' informations // read and store the downloaded rss' informations
void RssFeed::handleFinishedDownload(const QString &url, const QString &filePath) void RssFeed::handleFinishedDownload(const QString &url, const QString &filePath)
{ {
if (url == m_url) { if (url == m_url) {
qDebug() << Q_FUNC_INFO << "Successfully downloaded RSS feed at" << url; qDebug() << Q_FUNC_INFO << "Successfully downloaded RSS feed at" << url;
// Parse the download RSS // Parse the download RSS
m_manager->rssParser()->parseRssFile(m_url, filePath); m_manager->rssParser()->parseRssFile(m_url, filePath);
} else if (url == m_iconUrl) { }
m_icon = filePath; else if (url == m_iconUrl) {
qDebug() << Q_FUNC_INFO << "icon path:" << m_icon; m_icon = filePath;
m_manager->forwardFeedIconChanged(m_url, m_icon); qDebug() << Q_FUNC_INFO << "icon path:" << m_icon;
} m_manager->forwardFeedIconChanged(m_url, m_icon);
}
} }
void RssFeed::handleDownloadFailure(const QString &url, const QString &error) void RssFeed::handleDownloadFailure(const QString &url, const QString &error)
{ {
if (url != m_url) return; if (url != m_url) return;
m_inErrorState = true; m_inErrorState = true;
m_loading = false; m_loading = false;
m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount); m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount);
qWarning() << "Failed to download RSS feed at" << url; qWarning() << "Failed to download RSS feed at" << url;
qWarning() << "Reason:" << error; qWarning() << "Reason:" << error;
} }
void RssFeed::handleFeedTitle(const QString& feedUrl, const QString& title) void RssFeed::handleFeedTitle(const QString &feedUrl, const QString &title)
{ {
if (feedUrl != m_url) if (feedUrl != m_url) return;
return; if (m_title == title) return;
if (m_title == title) m_title = title;
return;
m_title = title; // Notify that we now have something better than a URL to display
if (m_alias.isEmpty())
// Notify that we now have something better than a URL to display m_manager->forwardFeedInfosChanged(feedUrl, title, m_unreadCount);
if (m_alias.isEmpty())
m_manager->forwardFeedInfosChanged(feedUrl, title, m_unreadCount);
} }
void RssFeed::downloadArticleTorrentIfMatching(RssDownloadRuleList* rules, const RssArticlePtr& article) void RssFeed::downloadArticleTorrentIfMatching(RssDownloadRuleList *rules, const RssArticlePtr &article)
{ {
Q_ASSERT(Preferences::instance()->isRssDownloadingEnabled()); Q_ASSERT(Preferences::instance()->isRssDownloadingEnabled());
RssDownloadRulePtr matching_rule = rules->findMatchingRule(m_url, article->title()); RssDownloadRulePtr matchingRule = rules->findMatchingRule(m_url, article->title());
if (!matching_rule) if (!matchingRule) return;
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;
}
}
}
if (matching_rule->ignoreDays() > 0) { matchingRule->setLastMatch(QDateTime::currentDateTime());
QDateTime lastMatch = matching_rule->lastMatch(); rules->saveRulesToStorage();
if (lastMatch.isValid()) { // Download the torrent
if (QDateTime::currentDateTime() < lastMatch.addDays(matching_rule->ignoreDays())) { const QString &torrentUrl = article->torrentUrl();
connect(article.data(), SIGNAL(articleWasRead()), SLOT(handleArticleStateChanged()), Qt::UniqueConnection); 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(); article->markAsRead();
return; return;
}
} }
}
matching_rule->setLastMatch(QDateTime::currentDateTime()); Logger::instance()->addMessage(tr("Automatically downloading '%1' torrent from '%2' RSS feed...").arg(article->title()).arg(displayName()));
rules->saveRulesToStorage(); connect(BitTorrent::Session::instance(), SIGNAL(downloadFromUrlFinished(QString)), article.data(), SLOT(handleTorrentDownloadSuccess(const QString &)), Qt::UniqueConnection);
// Download the torrent if (BitTorrent::MagnetUri(torrent_url).isValid())
const QString& torrent_url = article->torrentUrl(); article->markAsRead();
if (torrent_url.isEmpty()) { else
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); connect(BitTorrent::Session::instance(), SIGNAL(downloadFromUrlFinished(QString)), article.data(), SLOT(handleTorrentDownloadSuccess(const QString&)), Qt::UniqueConnection);
article->markAsRead();
return; BitTorrent::AddTorrentParams params;
} params.savePath = matchingRule->savePath();
params.label = matchingRule->label();
Logger::instance()->addMessage(tr("Automatically downloading '%1' torrent from '%2' RSS feed...").arg(article->title()).arg(displayName())); if (matchingRule->addPaused() == RssDownloadRule::ALWAYS_PAUSED)
connect(article.data(), SIGNAL(articleWasRead()), SLOT(handleArticleStateChanged()), Qt::UniqueConnection); params.addPaused = TriStateBool::True;
if (BitTorrent::MagnetUri(torrent_url).isValid()) else if (matchingRule->addPaused() == RssDownloadRule::NEVER_PAUSED)
article->markAsRead(); params.addPaused = TriStateBool::False;
else BitTorrent::Session::instance()->addTorrent(torrentUrl, params);
connect(BitTorrent::Session::instance(), SIGNAL(downloadFromUrlFinished(QString)), article.data(), SLOT(handleTorrentDownloadSuccess(const QString&)), Qt::UniqueConnection);
BitTorrent::AddTorrentParams params;
params.savePath = matching_rule->savePath();
params.label = matching_rule->label();
if (matching_rule->addPaused() == RssDownloadRule::ALWAYS_PAUSED)
params.addPaused = TriStateBool::True;
else if (matching_rule->addPaused() == RssDownloadRule::NEVER_PAUSED)
params.addPaused = TriStateBool::False;
BitTorrent::Session::instance()->addTorrent(torrent_url, params);
} }
void RssFeed::recheckRssItemsForDownload() void RssFeed::recheckRssItemsForDownload()
{ {
Q_ASSERT(Preferences::instance()->isRssDownloadingEnabled()); Q_ASSERT(Preferences::instance()->isRssDownloadingEnabled());
RssDownloadRuleList* rules = m_manager->downloadRules(); RssDownloadRuleList *rules = m_manager->downloadRules();
foreach (const RssArticlePtr& article, m_articlesByDate) { foreach (const RssArticlePtr &article, m_articlesByDate) {
if (!article->isRead()) if (!article->isRead())
downloadArticleTorrentIfMatching(rules, article); downloadArticleTorrentIfMatching(rules, article);
} }
} }
void RssFeed::handleNewArticle(const QString& feedUrl, const QVariantHash& articleData) void RssFeed::handleNewArticle(const QString &feedUrl, const QVariantHash &articleData)
{ {
if (feedUrl != m_url) if (feedUrl != m_url) return;
return;
RssArticlePtr article = hashToRssArticle(this, articleData); RssArticlePtr article = RssArticle::fromHash(this, articleData);
if (article.isNull()) { if (article.isNull()) {
qDebug() << "Article hash corrupted or guid is uncomputable; feed url: " << feedUrl; qDebug() << "Article hash corrupted or guid is uncomputable; feed url: " << feedUrl;
return; return;
} }
Q_ASSERT(article); Q_ASSERT(article);
addArticle(article); addArticle(article);
m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount); m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount);
// FIXME: We should forward the information here but this would seriously decrease // FIXME: We should forward the information here but this would seriously decrease
// performance with current design. // performance with current design.
//m_manager->forwardFeedContentChanged(m_url); //m_manager->forwardFeedContentChanged(m_url);
} }
void RssFeed::handleFeedParsingFinished(const QString& feedUrl, const QString& error) void RssFeed::handleFeedParsingFinished(const QString &feedUrl, const QString &error)
{ {
if (feedUrl != m_url) if (feedUrl != m_url) return;
return;
if (!error.isEmpty()) { if (!error.isEmpty()) {
qWarning() << "Failed to parse RSS feed at" << feedUrl; qWarning() << "Failed to parse RSS feed at" << feedUrl;
qWarning() << "Reason:" << error; qWarning() << "Reason:" << error;
} }
m_loading = false; m_loading = false;
m_inErrorState = !error.isEmpty(); m_inErrorState = !error.isEmpty();
m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount); m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount);
// XXX: Would not be needed if we did this in handleNewArticle() instead // XXX: Would not be needed if we did this in handleNewArticle() instead
m_manager->forwardFeedContentChanged(m_url); m_manager->forwardFeedContentChanged(m_url);
saveItemsToDisk(); saveItemsToDisk();
} }
void RssFeed::handleArticleStateChanged() void RssFeed::handleArticleStateChanged()
{ {
m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount); m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount);
} }
void RssFeed::decrementUnreadCount() void RssFeed::decrementUnreadCount()
{ {
--m_unreadCount; --m_unreadCount;
} }

114
src/base/rss/rssfeed.h

@ -1,6 +1,7 @@
/* /*
* Bittorrent Client using Qt4 and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere * 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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -39,6 +40,7 @@
#include "rssfile.h" #include "rssfile.h"
class RssFolder;
class RssFeed; class RssFeed;
class RssManager; class RssManager;
class RssDownloadRuleList; class RssDownloadRuleList;
@ -47,69 +49,69 @@ typedef QHash<QString, RssArticlePtr> RssArticleHash;
typedef QSharedPointer<RssFeed> RssFeedPtr; typedef QSharedPointer<RssFeed> RssFeedPtr;
typedef QList<RssFeedPtr> RssFeedList; typedef QList<RssFeedPtr> RssFeedList;
bool rssArticleDateRecentThan(const RssArticlePtr& left, const RssArticlePtr& right); bool rssArticleDateRecentThan(const RssArticlePtr &left, const RssArticlePtr &right);
class RssFeed: public QObject, public RssFile { class RssFeed: public QObject, public RssFile
Q_OBJECT {
Q_OBJECT
public: public:
RssFeed(RssManager* manager, RssFolder* m_parent, const QString& url); RssFeed(RssManager *manager, RssFolder *parent, const QString &url);
virtual ~RssFeed(); ~RssFeed();
virtual RssFolder* parent() const { return m_parent; }
virtual void setParent(RssFolder* parent) { m_parent = parent; } RssFolder *parent() const;
bool refresh(); void setParent(RssFolder *parent);
virtual QString id() const { return m_url; } bool refresh();
virtual void removeAllSettings(); QString id() const;
virtual void saveItemsToDisk(); void removeAllSettings();
bool isLoading() const; void saveItemsToDisk();
QString title() const; bool isLoading() const;
virtual void rename(const QString &alias); QString title() const;
virtual QString displayName() const; void rename(const QString &newName);
QString url() const; QString displayName() const;
virtual QString iconPath() const; QString url() const;
bool hasCustomIcon() const; QString iconPath() const;
void setIconPath(const QString &pathHierarchy); bool hasCustomIcon() const;
RssArticlePtr getItem(const QString &guid) const; void setIconPath(const QString &pathHierarchy);
uint count() const; RssArticlePtr getItem(const QString &guid) const;
virtual void markAsRead(); uint count() const;
void markAsDirty(bool dirty = true); void markAsRead();
virtual uint unreadCount() const; void markAsDirty(bool dirty = true);
virtual RssArticleList articleListByDateDesc() const; uint unreadCount() const;
const RssArticleHash& articleHash() const { return m_articles; } RssArticleList articleListByDateDesc() const;
virtual RssArticleList unreadArticleListByDateDesc() const; const RssArticleHash &articleHash() const;
void decrementUnreadCount(); RssArticleList unreadArticleListByDateDesc() const;
void recheckRssItemsForDownload(); void decrementUnreadCount();
void recheckRssItemsForDownload();
private slots: private slots:
void handleFinishedDownload(const QString &url, const QString &filePath); void handleFinishedDownload(const QString &url, const QString &filePath);
void handleDownloadFailure(const QString &url, const QString &error); void handleDownloadFailure(const QString &url, const QString &error);
void handleFeedTitle(const QString& feedUrl, const QString& title); void handleFeedTitle(const QString &feedUrl, const QString &title);
void handleNewArticle(const QString& feedUrl, const QVariantHash& article); void handleNewArticle(const QString &feedUrl, const QVariantHash &article);
void handleFeedParsingFinished(const QString& feedUrl, const QString& error); void handleFeedParsingFinished(const QString &feedUrl, const QString &error);
void handleArticleStateChanged(); void handleArticleStateChanged();
private: private:
QString iconUrl() const; QString iconUrl() const;
void loadItemsFromDisk(); void loadItemsFromDisk();
void addArticle(const RssArticlePtr& article); void addArticle(const RssArticlePtr &article);
void downloadArticleTorrentIfMatching(RssDownloadRuleList* rules, const RssArticlePtr& article); void downloadArticleTorrentIfMatching(RssDownloadRuleList *rules, const RssArticlePtr &article);
private: private:
RssManager* m_manager; RssManager *m_manager;
RssArticleHash m_articles; RssArticleHash m_articles;
RssArticleList m_articlesByDate; // Articles sorted by date (more recent first) RssArticleList m_articlesByDate; // Articles sorted by date (more recent first)
RssFolder* m_parent; RssFolder *m_parent;
QString m_title; QString m_title;
QString m_url; QString m_url;
QString m_alias; QString m_alias;
QString m_icon; QString m_icon;
QString m_iconUrl; QString m_iconUrl;
uint m_unreadCount; uint m_unreadCount;
bool m_dirty; bool m_dirty;
bool m_inErrorState; bool m_inErrorState;
bool m_loading; bool m_loading;
}; };
#endif // RSSFEED_H #endif // RSSFEED_H

20
src/base/rss/rssfile.cpp

@ -1,6 +1,7 @@
/* /*
* Bittorrent Client using Qt4 and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere * 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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -31,10 +32,13 @@
#include "rssfolder.h" #include "rssfolder.h"
#include "rssfile.h" #include "rssfile.h"
QStringList RssFile::pathHierarchy() const { RssFile::~RssFile() {}
QStringList path;
if (parent()) QStringList RssFile::pathHierarchy() const
path << parent()->pathHierarchy(); {
path << id(); QStringList path;
return path; if (parent())
path << parent()->pathHierarchy();
path << id();
return path;
} }

43
src/base/rss/rssfile.h

@ -1,6 +1,7 @@
/* /*
* Bittorrent Client using Qt4 and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere * 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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -47,28 +48,30 @@ typedef QList<RssFilePtr> RssFileList;
/** /**
* Parent interface for RssFolder and RssFeed. * Parent interface for RssFolder and RssFeed.
*/ */
class RssFile { class RssFile
{
public: public:
virtual ~RssFile() {} virtual ~RssFile();
virtual uint unreadCount() const = 0; virtual uint unreadCount() const = 0;
virtual QString displayName() const = 0; virtual QString displayName() const = 0;
virtual QString id() const = 0; virtual QString id() const = 0;
virtual QString iconPath() const = 0; virtual QString iconPath() const = 0;
virtual void rename(const QString &new_name) = 0; virtual void rename(const QString &newName) = 0;
virtual void markAsRead() = 0; virtual void markAsRead() = 0;
virtual RssFolder* parent() const = 0; virtual RssFolder *parent() const = 0;
virtual void setParent(RssFolder* parent) = 0; virtual void setParent(RssFolder *parent) = 0;
virtual bool refresh() = 0; virtual bool refresh() = 0;
virtual RssArticleList articleListByDateDesc() const = 0; virtual RssArticleList articleListByDateDesc() const = 0;
virtual RssArticleList unreadArticleListByDateDesc() const = 0; virtual RssArticleList unreadArticleListByDateDesc() const = 0;
virtual void removeAllSettings() = 0; virtual void removeAllSettings() = 0;
virtual void saveItemsToDisk() = 0; virtual void saveItemsToDisk() = 0;
virtual void recheckRssItemsForDownload() = 0; virtual void recheckRssItemsForDownload() = 0;
QStringList pathHierarchy() const;
QStringList pathHierarchy() const;
protected: protected:
uint m_unreadCount; uint m_unreadCount;
}; };
#endif // RSSFILE_H #endif // RSSFILE_H

349
src/base/rss/rssfolder.cpp

@ -1,6 +1,7 @@
/* /*
* Bittorrent Client using Qt4 and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere * 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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -37,226 +38,256 @@
#include "rssarticle.h" #include "rssarticle.h"
#include "rssfolder.h" #include "rssfolder.h"
RssFolder::RssFolder(RssFolder *parent, const QString &name): m_parent(parent), m_name(name) { RssFolder::RssFolder(RssFolder *parent, const QString &name)
: m_parent(parent)
, m_name(name)
{
}
RssFolder::~RssFolder() {}
RssFolder *RssFolder::parent() const
{
return m_parent;
} }
RssFolder::~RssFolder() { void RssFolder::setParent(RssFolder *parent)
{
m_parent = parent;
} }
unsigned int RssFolder::unreadCount() const { uint RssFolder::unreadCount() const
uint nb_unread = 0; {
uint nbUnread = 0;
RssFileHash::ConstIterator it = m_children.begin();
RssFileHash::ConstIterator itend = m_children.end();
for ( ; it != itend; ++it)
nbUnread += it.value()->unreadCount();
RssFileHash::ConstIterator it = m_children.begin(); return nbUnread;
RssFileHash::ConstIterator itend = m_children.end();
for ( ; it != itend; ++it) {
nb_unread += it.value()->unreadCount();
}
return nb_unread;
} }
void RssFolder::removeChild(const QString &childId) { void RssFolder::removeChild(const QString &childId)
if (m_children.contains(childId)) { {
RssFilePtr child = m_children.take(childId); if (m_children.contains(childId)) {
child->removeAllSettings(); RssFilePtr child = m_children.take(childId);
} child->removeAllSettings();
}
} }
RssFolderPtr RssFolder::addFolder(const QString &name) { RssFolderPtr RssFolder::addFolder(const QString &name)
RssFolderPtr subfolder; {
if (!m_children.contains(name)) { RssFolderPtr subfolder;
subfolder = RssFolderPtr(new RssFolder(this, name)); if (!m_children.contains(name)) {
m_children[name] = subfolder; subfolder = RssFolderPtr(new RssFolder(this, name));
} else { m_children[name] = subfolder;
subfolder = qSharedPointerDynamicCast<RssFolder>(m_children.value(name)); }
} else {
return subfolder; subfolder = qSharedPointerDynamicCast<RssFolder>(m_children.value(name));
}
return subfolder;
} }
RssFeedPtr RssFolder::addStream(RssManager* manager, const QString &url) { RssFeedPtr RssFolder::addStream(RssManager *manager, const QString &url)
qDebug() << Q_FUNC_INFO << manager << url; {
RssFeedPtr stream(new RssFeed(manager, this, url)); qDebug() << Q_FUNC_INFO << manager << url;
Q_ASSERT(stream); RssFeedPtr stream(new RssFeed(manager, this, url));
qDebug() << "Stream URL is " << stream->url(); Q_ASSERT(stream);
Q_ASSERT(!m_children.contains(stream->url())); qDebug() << "Stream URL is " << stream->url();
m_children[stream->url()] = stream; Q_ASSERT(!m_children.contains(stream->url()));
stream->refresh(); m_children[stream->url()] = stream;
return stream; stream->refresh();
return stream;
} }
// Refresh All Children // Refresh All Children
bool RssFolder::refresh() { bool RssFolder::refresh()
RssFileHash::ConstIterator it = m_children.begin(); {
RssFileHash::ConstIterator itend = m_children.end(); RssFileHash::ConstIterator it = m_children.begin();
bool refreshed = false; RssFileHash::ConstIterator itend = m_children.end();
for ( ; it != itend; ++it) { bool refreshed = false;
if (it.value()->refresh()) for ( ; it != itend; ++it) {
refreshed = true; if (it.value()->refresh())
} refreshed = true;
return refreshed; }
return refreshed;
} }
RssArticleList RssFolder::articleListByDateDesc() const { RssArticleList RssFolder::articleListByDateDesc() const
RssArticleList news; {
RssArticleList news;
RssFileHash::ConstIterator it = m_children.begin();
RssFileHash::ConstIterator itend = m_children.end(); RssFileHash::ConstIterator it = m_children.begin();
for ( ; it != itend; ++it) { RssFileHash::ConstIterator itend = m_children.end();
int n = news.size(); for ( ; it != itend; ++it) {
news << it.value()->articleListByDateDesc(); int n = news.size();
std::inplace_merge(news.begin(), news.begin() + n, news.end(), rssArticleDateRecentThan); news << it.value()->articleListByDateDesc();
} std::inplace_merge(news.begin(), news.begin() + n, news.end(), rssArticleDateRecentThan);
return news; }
return news;
} }
RssArticleList RssFolder::unreadArticleListByDateDesc() const { RssArticleList RssFolder::unreadArticleListByDateDesc() const
RssArticleList unread_news; {
RssArticleList unreadNews;
RssFileHash::ConstIterator it = m_children.begin();
RssFileHash::ConstIterator itend = m_children.end(); RssFileHash::ConstIterator it = m_children.begin();
for ( ; it != itend; ++it) { RssFileHash::ConstIterator itend = m_children.end();
int n = unread_news.size(); for ( ; it != itend; ++it) {
unread_news << it.value()->unreadArticleListByDateDesc(); int n = unreadNews.size();
std::inplace_merge(unread_news.begin(), unread_news.begin() + n, unread_news.end(), rssArticleDateRecentThan); unreadNews << it.value()->unreadArticleListByDateDesc();
} std::inplace_merge(unreadNews.begin(), unreadNews.begin() + n, unreadNews.end(), rssArticleDateRecentThan);
return unread_news; }
return unreadNews;
} }
RssFileList RssFolder::getContent() const { RssFileList RssFolder::getContent() const
return m_children.values(); {
return m_children.values();
} }
unsigned int RssFolder::getNbFeeds() const { uint RssFolder::getNbFeeds() const
uint nbFeeds = 0; {
uint nbFeeds = 0;
RssFileHash::ConstIterator it = m_children.begin();
RssFileHash::ConstIterator itend = m_children.end(); RssFileHash::ConstIterator it = m_children.begin();
for ( ; it != itend; ++it) { RssFileHash::ConstIterator itend = m_children.end();
if (RssFolderPtr folder = qSharedPointerDynamicCast<RssFolder>(it.value())) for ( ; it != itend; ++it) {
nbFeeds += folder->getNbFeeds(); if (RssFolderPtr folder = qSharedPointerDynamicCast<RssFolder>(it.value()))
else nbFeeds += folder->getNbFeeds();
++nbFeeds; // Feed else
} ++nbFeeds; // Feed
return nbFeeds; }
return nbFeeds;
} }
QString RssFolder::displayName() const { QString RssFolder::displayName() const
return m_name; {
return m_name;
} }
void RssFolder::rename(const QString &new_name) { void RssFolder::rename(const QString &newName)
if (m_name == new_name) return; {
Q_ASSERT(!m_parent->hasChild(new_name)); if (m_name == newName) return;
if (!m_parent->hasChild(new_name)) {
// Update parent Q_ASSERT(!m_parent->hasChild(newName));
m_parent->renameChildFolder(m_name, new_name); if (!m_parent->hasChild(newName)) {
// Actually rename // Update parent
m_name = new_name; m_parent->renameChildFolder(m_name, newName);
} // Actually rename
m_name = newName;
}
} }
void RssFolder::markAsRead() { void RssFolder::markAsRead()
RssFileHash::ConstIterator it = m_children.begin(); {
RssFileHash::ConstIterator itend = m_children.end(); RssFileHash::ConstIterator it = m_children.begin();
for ( ; it != itend; ++it) { RssFileHash::ConstIterator itend = m_children.end();
it.value()->markAsRead(); for ( ; it != itend; ++it) {
} it.value()->markAsRead();
}
} }
RssFeedList RssFolder::getAllFeeds() const { RssFeedList RssFolder::getAllFeeds() const
RssFeedList streams; {
RssFeedList streams;
RssFileHash::ConstIterator it = m_children.begin();
RssFileHash::ConstIterator itend = m_children.end(); RssFileHash::ConstIterator it = m_children.begin();
for ( ; it != itend; ++it) { RssFileHash::ConstIterator itend = m_children.end();
if (RssFeedPtr feed = qSharedPointerDynamicCast<RssFeed>(it.value())) { for ( ; it != itend; ++it) {
streams << feed; if (RssFeedPtr feed = qSharedPointerDynamicCast<RssFeed>(it.value()))
} else if (RssFolderPtr folder = qSharedPointerDynamicCast<RssFolder>(it.value())) { streams << feed;
streams << folder->getAllFeeds(); else if (RssFolderPtr folder = qSharedPointerDynamicCast<RssFolder>(it.value()))
streams << folder->getAllFeeds();
} }
} return streams;
return streams;
} }
QHash<QString, RssFeedPtr> RssFolder::getAllFeedsAsHash() const { QHash<QString, RssFeedPtr> RssFolder::getAllFeedsAsHash() const
QHash<QString, RssFeedPtr> ret; {
QHash<QString, RssFeedPtr> ret;
RssFileHash::ConstIterator it = m_children.begin();
RssFileHash::ConstIterator itend = m_children.end(); RssFileHash::ConstIterator it = m_children.begin();
for ( ; it != itend; ++it) { RssFileHash::ConstIterator itend = m_children.end();
if (RssFeedPtr feed = qSharedPointerDynamicCast<RssFeed>(it.value())) { for ( ; it != itend; ++it) {
qDebug() << Q_FUNC_INFO << feed->url(); if (RssFeedPtr feed = qSharedPointerDynamicCast<RssFeed>(it.value())) {
ret[feed->url()] = feed; qDebug() << Q_FUNC_INFO << feed->url();
} else if (RssFolderPtr folder = qSharedPointerDynamicCast<RssFolder>(it.value())) { ret[feed->url()] = feed;
ret.unite(folder->getAllFeedsAsHash()); }
else if (RssFolderPtr folder = qSharedPointerDynamicCast<RssFolder>(it.value())) {
ret.unite(folder->getAllFeedsAsHash());
}
} }
} return ret;
return ret;
} }
void RssFolder::addFile(const RssFilePtr& item) { void RssFolder::addFile(const RssFilePtr &item)
if (RssFeedPtr feed = qSharedPointerDynamicCast<RssFeed>(item)) { {
Q_ASSERT(!m_children.contains(feed->url())); if (RssFeedPtr feed = qSharedPointerDynamicCast<RssFeed>(item)) {
m_children[feed->url()] = item; Q_ASSERT(!m_children.contains(feed->url()));
qDebug("Added feed %s to folder ./%s", qPrintable(feed->url()), qPrintable(m_name)); m_children[feed->url()] = item;
} else if (RssFolderPtr folder = qSharedPointerDynamicCast<RssFolder>(item)) { qDebug("Added feed %s to folder ./%s", qPrintable(feed->url()), qPrintable(m_name));
Q_ASSERT(!m_children.contains(folder->displayName())); }
m_children[folder->displayName()] = item; else if (RssFolderPtr folder = qSharedPointerDynamicCast<RssFolder>(item)) {
qDebug("Added folder %s to folder ./%s", qPrintable(folder->displayName()), qPrintable(m_name)); Q_ASSERT(!m_children.contains(folder->displayName()));
} m_children[folder->displayName()] = item;
// Update parent qDebug("Added folder %s to folder ./%s", qPrintable(folder->displayName()), qPrintable(m_name));
item->setParent(this); }
// Update parent
item->setParent(this);
} }
void RssFolder::removeAllItems() { void RssFolder::removeAllItems()
m_children.clear(); {
m_children.clear();
} }
void RssFolder::removeAllSettings() { void RssFolder::removeAllSettings()
RssFileHash::ConstIterator it = m_children.begin(); {
RssFileHash::ConstIterator itend = m_children.end(); RssFileHash::ConstIterator it = m_children.begin();
for ( ; it != itend; ++it) { RssFileHash::ConstIterator itend = m_children.end();
it.value()->removeAllSettings(); for ( ; it != itend; ++it)
} it.value()->removeAllSettings();
} }
void RssFolder::saveItemsToDisk() void RssFolder::saveItemsToDisk()
{ {
foreach (const RssFilePtr& child, m_children.values()) { foreach (const RssFilePtr &child, m_children.values())
child->saveItemsToDisk(); child->saveItemsToDisk();
}
} }
QString RssFolder::id() const QString RssFolder::id() const
{ {
return m_name; return m_name;
} }
QString RssFolder::iconPath() const QString RssFolder::iconPath() const
{ {
return IconProvider::instance()->getIconPath("inode-directory"); return IconProvider::instance()->getIconPath("inode-directory");
} }
bool RssFolder::hasChild(const QString &childId) { bool RssFolder::hasChild(const QString &childId)
return m_children.contains(childId); {
return m_children.contains(childId);
} }
void RssFolder::renameChildFolder(const QString &old_name, const QString &new_name) void RssFolder::renameChildFolder(const QString &oldName, const QString &newName)
{ {
Q_ASSERT(m_children.contains(old_name)); Q_ASSERT(m_children.contains(oldName));
RssFilePtr folder = m_children.take(old_name); RssFilePtr folder = m_children.take(oldName);
m_children[new_name] = folder; m_children[newName] = folder;
} }
RssFilePtr RssFolder::takeChild(const QString &childId) RssFilePtr RssFolder::takeChild(const QString &childId)
{ {
return m_children.take(childId); return m_children.take(childId);
} }
void RssFolder::recheckRssItemsForDownload() void RssFolder::recheckRssItemsForDownload()
{ {
RssFileHash::ConstIterator it = m_children.begin(); RssFileHash::ConstIterator it = m_children.begin();
RssFileHash::ConstIterator itend = m_children.end(); RssFileHash::ConstIterator itend = m_children.end();
for ( ; it != itend; ++it) { for ( ; it != itend; ++it)
it.value()->recheckRssItemsForDownload(); it.value()->recheckRssItemsForDownload();
}
} }

73
src/base/rss/rssfolder.h

@ -1,6 +1,7 @@
/* /*
* Bittorrent Client using Qt4 and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere * 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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -45,45 +46,47 @@ typedef QSharedPointer<RssFeed> RssFeedPtr;
typedef QSharedPointer<RssFolder> RssFolderPtr; typedef QSharedPointer<RssFolder> RssFolderPtr;
typedef QList<RssFeedPtr> RssFeedList; typedef QList<RssFeedPtr> RssFeedList;
class RssFolder: public QObject, public RssFile { class RssFolder: public QObject, public RssFile
Q_OBJECT {
Q_OBJECT
public: public:
RssFolder(RssFolder *parent = 0, const QString &name = QString()); explicit RssFolder(RssFolder *parent = 0, const QString &name = QString());
virtual ~RssFolder(); ~RssFolder();
virtual RssFolder* parent() const { return m_parent; }
virtual void setParent(RssFolder* parent) { m_parent = parent; } RssFolder *parent() const;
virtual unsigned int unreadCount() const; void setParent(RssFolder *parent);
RssFeedPtr addStream(RssManager* manager, const QString &url); uint unreadCount() const;
RssFolderPtr addFolder(const QString &name); RssFeedPtr addStream(RssManager *manager, const QString &url);
unsigned int getNbFeeds() const; RssFolderPtr addFolder(const QString &name);
RssFileList getContent() const; uint getNbFeeds() const;
RssFeedList getAllFeeds() const; RssFileList getContent() const;
QHash<QString, RssFeedPtr> getAllFeedsAsHash() const; RssFeedList getAllFeeds() const;
virtual QString displayName() const; QHash<QString, RssFeedPtr> getAllFeedsAsHash() const;
virtual QString id() const; QString displayName() const;
virtual QString iconPath() const; QString id() const;
bool hasChild(const QString &childId); QString iconPath() const;
virtual RssArticleList articleListByDateDesc() const; bool hasChild(const QString &childId);
virtual RssArticleList unreadArticleListByDateDesc() const; RssArticleList articleListByDateDesc() const;
virtual void removeAllSettings(); RssArticleList unreadArticleListByDateDesc() const;
virtual void saveItemsToDisk(); void removeAllSettings();
void removeAllItems(); void saveItemsToDisk();
void renameChildFolder(const QString &old_name, const QString &new_name); void removeAllItems();
RssFilePtr takeChild(const QString &childId); void renameChildFolder(const QString &oldName, const QString &newName);
void recheckRssItemsForDownload(); RssFilePtr takeChild(const QString &childId);
void recheckRssItemsForDownload();
public slots: public slots:
virtual bool refresh(); bool refresh();
void addFile(const RssFilePtr& item); void addFile(const RssFilePtr &item);
void removeChild(const QString &childId); void removeChild(const QString &childId);
virtual void rename(const QString &new_name); void rename(const QString &newName);
virtual void markAsRead(); void markAsRead();
private: private:
RssFolder *m_parent; RssFolder *m_parent;
QString m_name; QString m_name;
RssFileHash m_children; RssFileHash m_children;
}; };
#endif // RSSFOLDER_H #endif // RSSFOLDER_H

171
src/base/rss/rssmanager.cpp

@ -1,6 +1,7 @@
/* /*
* Bittorrent Client using Qt4 and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere * 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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -30,8 +31,8 @@
#include <QDebug> #include <QDebug>
#include "base/logger.h"
#include "base/preferences.h" #include "base/preferences.h"
#include "base/bittorrent/session.h"
#include "rssfeed.h" #include "rssfeed.h"
#include "rssarticle.h" #include "rssarticle.h"
#include "rssdownloadrulelist.h" #include "rssdownloadrulelist.h"
@ -40,122 +41,124 @@
static const int MSECS_PER_MIN = 60000; static const int MSECS_PER_MIN = 60000;
RssManager::RssManager(): RssManager::RssManager()
m_downloadRules(new RssDownloadRuleList), : m_downloadRules(new RssDownloadRuleList)
m_rssParser(new RssParser(this)) , m_rssParser(new RssParser(this))
{ {
connect(&m_refreshTimer, SIGNAL(timeout()), SLOT(refresh())); connect(&m_refreshTimer, SIGNAL(timeout()), SLOT(refresh()));
m_refreshInterval = Preferences::instance()->getRSSRefreshInterval(); m_refreshInterval = Preferences::instance()->getRSSRefreshInterval();
m_refreshTimer.start(m_refreshInterval * MSECS_PER_MIN); m_refreshTimer.start(m_refreshInterval * MSECS_PER_MIN);
} }
RssManager::~RssManager() RssManager::~RssManager()
{ {
qDebug("Deleting RSSManager..."); qDebug("Deleting RSSManager...");
delete m_downloadRules; delete m_downloadRules;
delete m_rssParser; delete m_rssParser;
saveItemsToDisk(); saveItemsToDisk();
saveStreamList(); saveStreamList();
qDebug("RSSManager deleted"); qDebug("RSSManager deleted");
} }
RssParser* RssManager::rssParser() const RssParser *RssManager::rssParser() const
{ {
return m_rssParser; return m_rssParser;
} }
void RssManager::updateRefreshInterval(uint val) void RssManager::updateRefreshInterval(uint val)
{ {
if (m_refreshInterval != val) { if (m_refreshInterval != val) {
m_refreshInterval = val; m_refreshInterval = val;
m_refreshTimer.start(m_refreshInterval*60000); m_refreshTimer.start(m_refreshInterval*60000);
qDebug("New RSS refresh interval is now every %dmin", m_refreshInterval); qDebug("New RSS refresh interval is now every %dmin", m_refreshInterval);
} }
} }
void RssManager::loadStreamList() void RssManager::loadStreamList()
{ {
const Preferences* const pref = Preferences::instance(); const Preferences *const pref = Preferences::instance();
const QStringList streamsUrl = pref->getRssFeedsUrls(); const QStringList streamsUrl = pref->getRssFeedsUrls();
const QStringList aliases = pref->getRssFeedsAliases(); const QStringList aliases = pref->getRssFeedsAliases();
if (streamsUrl.size() != aliases.size()) { if (streamsUrl.size() != aliases.size()) {
std::cerr << "Corrupted Rss list, not loading it\n"; Logger::instance()->addMessage("Corrupted RSS list, not loading it.", Log::WARNING);
return; return;
}
uint i = 0;
qDebug() << Q_FUNC_INFO << streamsUrl;
foreach (QString s, streamsUrl) {
QStringList path = s.split("\\", QString::SkipEmptyParts);
if (path.empty()) continue;
const QString feed_url = path.takeLast();
qDebug() << "Feed URL:" << feed_url;
// Create feed path (if it does not exists)
RssFolder* feed_parent = this;
foreach (const QString &folder_name, path) {
qDebug() << "Adding parent folder:" << folder_name;
feed_parent = feed_parent->addFolder(folder_name).data();
} }
// Create feed
qDebug() << "Adding feed to parent folder"; uint i = 0;
RssFeedPtr stream = feed_parent->addStream(this, feed_url); qDebug() << Q_FUNC_INFO << streamsUrl;
const QString& alias = aliases[i]; foreach (QString s, streamsUrl) {
if (!alias.isEmpty()) { QStringList path = s.split("\\", QString::SkipEmptyParts);
stream->rename(alias); if (path.empty()) continue;
const QString feedUrl = path.takeLast();
qDebug() << "Feed URL:" << feedUrl;
// Create feed path (if it does not exists)
RssFolder *feedParent = this;
foreach (const QString &folderName, path) {
qDebug() << "Adding parent folder:" << folderName;
feedParent = feedParent->addFolder(folderName).data();
}
// Create feed
qDebug() << "Adding feed to parent folder";
RssFeedPtr stream = feedParent->addStream(this, feedUrl);
const QString &alias = aliases[i];
if (!alias.isEmpty())
stream->rename(alias);
++i;
} }
++i; qDebug("NB RSS streams loaded: %d", streamsUrl.size());
}
qDebug("NB RSS streams loaded: %d", streamsUrl.size());
} }
void RssManager::forwardFeedContentChanged(const QString& url) void RssManager::forwardFeedContentChanged(const QString &url)
{ {
emit feedContentChanged(url); emit feedContentChanged(url);
} }
void RssManager::forwardFeedInfosChanged(const QString& url, const QString& displayName, uint unreadCount) void RssManager::forwardFeedInfosChanged(const QString &url, const QString &displayName, uint unreadCount)
{ {
emit feedInfosChanged(url, displayName, unreadCount); emit feedInfosChanged(url, displayName, unreadCount);
} }
void RssManager::forwardFeedIconChanged(const QString& url, const QString& iconPath) void RssManager::forwardFeedIconChanged(const QString &url, const QString &iconPath)
{ {
emit feedIconChanged(url, iconPath); emit feedIconChanged(url, iconPath);
} }
void RssManager::moveFile(const RssFilePtr& file, const RssFolderPtr& destinationFolder) void RssManager::moveFile(const RssFilePtr &file, const RssFolderPtr &destinationFolder)
{ {
RssFolder* src_folder = file->parent(); RssFolder *srcFolder = file->parent();
if (destinationFolder != src_folder) { if (destinationFolder != srcFolder) {
// Remove reference in old folder // Remove reference in old folder
src_folder->takeChild(file->id()); srcFolder->takeChild(file->id());
// add to new Folder // add to new Folder
destinationFolder->addFile(file); destinationFolder->addFile(file);
} else { }
qDebug("Nothing to move, same destination folder"); else {
} qDebug("Nothing to move, same destination folder");
}
} }
void RssManager::saveStreamList() const void RssManager::saveStreamList() const
{ {
QStringList streamsUrl; QStringList streamsUrl;
QStringList aliases; QStringList aliases;
RssFeedList streams = getAllFeeds(); RssFeedList streams = getAllFeeds();
foreach (const RssFeedPtr& stream, streams) { foreach (const RssFeedPtr &stream, streams) {
// This backslash has nothing to do with path handling // This backslash has nothing to do with path handling
QString stream_path = stream->pathHierarchy().join("\\"); QString streamPath = stream->pathHierarchy().join("\\");
if (stream_path.isNull()) if (streamPath.isNull())
stream_path = ""; streamPath = "";
qDebug("Saving stream path: %s", qPrintable(stream_path)); qDebug("Saving stream path: %s", qPrintable(streamPath));
streamsUrl << stream_path; streamsUrl << streamPath;
aliases << stream->displayName(); aliases << stream->displayName();
} }
Preferences* const pref = Preferences::instance(); Preferences *const pref = Preferences::instance();
pref->setRssFeedsUrls(streamsUrl); pref->setRssFeedsUrls(streamsUrl);
pref->setRssFeedsAliases(aliases); pref->setRssFeedsAliases(aliases);
} }
RssDownloadRuleList* RssManager::downloadRules() const RssDownloadRuleList *RssManager::downloadRules() const
{ {
Q_ASSERT(m_downloadRules); Q_ASSERT(m_downloadRules);
return m_downloadRules; return m_downloadRules;
} }

48
src/base/rss/rssmanager.h

@ -1,6 +1,7 @@
/* /*
* Bittorrent Client using Qt4 and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere * 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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -38,39 +39,40 @@
class RssDownloadRuleList; class RssDownloadRuleList;
class RssParser; class RssParser;
class RssManager; class RssManager;
typedef QSharedPointer<RssManager> RssManagerPtr; typedef QSharedPointer<RssManager> RssManagerPtr;
class RssManager: public RssFolder { class RssManager: public RssFolder
Q_OBJECT {
Q_OBJECT
public: public:
RssManager(); RssManager();
virtual ~RssManager(); ~RssManager();
RssParser* rssParser() const; RssParser *rssParser() const;
RssDownloadRuleList* downloadRules() const; RssDownloadRuleList *downloadRules() const;
public slots: public slots:
void loadStreamList(); void loadStreamList();
void saveStreamList() const; void saveStreamList() const;
void forwardFeedContentChanged(const QString& url); void forwardFeedContentChanged(const QString &url);
void forwardFeedInfosChanged(const QString& url, const QString& displayName, uint unreadCount); void forwardFeedInfosChanged(const QString &url, const QString &displayName, uint unreadCount);
void forwardFeedIconChanged(const QString& url, const QString& iconPath); void forwardFeedIconChanged(const QString &url, const QString &iconPath);
void moveFile(const RssFilePtr& file, const RssFolderPtr& destinationFolder); void moveFile(const RssFilePtr &file, const RssFolderPtr &destinationFolder);
void updateRefreshInterval(uint val); void updateRefreshInterval(uint val);
signals: signals:
void feedContentChanged(const QString& url); void feedContentChanged(const QString &url);
void feedInfosChanged(const QString& url, const QString& displayName, uint unreadCount); void feedInfosChanged(const QString &url, const QString &displayName, uint unreadCount);
void feedIconChanged(const QString& url, const QString& iconPath); void feedIconChanged(const QString &url, const QString &iconPath);
private: private:
QTimer m_refreshTimer; QTimer m_refreshTimer;
uint m_refreshInterval; uint m_refreshInterval;
RssDownloadRuleList* m_downloadRules; RssDownloadRuleList *m_downloadRules;
RssParser* m_rssParser; RssParser *m_rssParser;
}; };
#endif // RSSMANAGER_H #endif // RSSMANAGER_H

834
src/base/rss/rssparser.cpp

@ -37,475 +37,505 @@
#include "base/utils/fs.h" #include "base/utils/fs.h"
#include "rssparser.h" #include "rssparser.h"
struct ParsingJob { struct ParsingJob
QString feedUrl; {
QString filePath; QString feedUrl;
QString filePath;
}; };
static const char shortDay[][4] = { static const char shortDay[][4] = {
"Mon", "Tue", "Wed", "Mon", "Tue", "Wed",
"Thu", "Fri", "Sat", "Thu", "Fri", "Sat",
"Sun" "Sun"
}; };
static const char longDay[][10] = { static const char longDay[][10] = {
"Monday", "Tuesday", "Wednesday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday", "Thursday", "Friday", "Saturday",
"Sunday" "Sunday"
}; };
static const char shortMonth[][4] = { static const char shortMonth[][4] = {
"Jan", "Feb", "Mar", "Apr", "Jan", "Feb", "Mar", "Apr",
"May", "Jun", "Jul", "Aug", "May", "Jun", "Jul", "Aug",
"Sep", "Oct", "Nov", "Dec" "Sep", "Oct", "Nov", "Dec"
}; };
// Ported to Qt4 from KDElibs4 // Ported to Qt from KDElibs4
QDateTime RssParser::parseDate(const QString &string) { QDateTime RssParser::parseDate(const QString &string)
const QString str = string.trimmed(); {
if (str.isEmpty()) const QString str = string.trimmed();
return QDateTime::currentDateTime(); if (str.isEmpty())
return QDateTime::currentDateTime();
int nyear = 6; // indexes within string to values
int nmonth = 4; int nyear = 6; // indexes within string to values
int nday = 2; int nmonth = 4;
int nwday = 1; int nday = 2;
int nhour = 7; int nwday = 1;
int nmin = 8; int nhour = 7;
int nsec = 9; int nmin = 8;
// Also accept obsolete form "Weekday, DD-Mon-YY HH:MM:SS ±hhmm" int nsec = 9;
QRegExp rx("^(?:([A-Z][a-z]+),\\s*)?(\\d{1,2})(\\s+|-)([^-\\s]+)(\\s+|-)(\\d{2,4})\\s+(\\d\\d):(\\d\\d)(?::(\\d\\d))?\\s+(\\S+)$"); // Also accept obsolete form "Weekday, DD-Mon-YY HH:MM:SS ±hhmm"
QStringList parts; QRegExp rx("^(?:([A-Z][a-z]+),\\s*)?(\\d{1,2})(\\s+|-)([^-\\s]+)(\\s+|-)(\\d{2,4})\\s+(\\d\\d):(\\d\\d)(?::(\\d\\d))?\\s+(\\S+)$");
if (!str.indexOf(rx)) { QStringList parts;
// Check that if date has '-' separators, both separators are '-'. if (!str.indexOf(rx)) {
parts = rx.capturedTexts(); // Check that if date has '-' separators, both separators are '-'.
bool h1 = (parts[3] == QLatin1String("-")); parts = rx.capturedTexts();
bool h2 = (parts[5] == QLatin1String("-")); bool h1 = (parts[3] == QLatin1String("-"));
if (h1 != h2) bool h2 = (parts[5] == QLatin1String("-"));
return QDateTime::currentDateTime(); if (h1 != h2)
} else { return QDateTime::currentDateTime();
// Check for the obsolete form "Wdy Mon DD HH:MM:SS YYYY" }
rx = QRegExp("^([A-Z][a-z]+)\\s+(\\S+)\\s+(\\d\\d)\\s+(\\d\\d):(\\d\\d):(\\d\\d)\\s+(\\d\\d\\d\\d)$"); else {
if (str.indexOf(rx)) // Check for the obsolete form "Wdy Mon DD HH:MM:SS YYYY"
return QDateTime::currentDateTime(); rx = QRegExp("^([A-Z][a-z]+)\\s+(\\S+)\\s+(\\d\\d)\\s+(\\d\\d):(\\d\\d):(\\d\\d)\\s+(\\d\\d\\d\\d)$");
nyear = 7; if (str.indexOf(rx))
nmonth = 2; return QDateTime::currentDateTime();
nday = 3; nyear = 7;
nwday = 1; nmonth = 2;
nhour = 4; nday = 3;
nmin = 5; nwday = 1;
nsec = 6; nhour = 4;
parts = rx.capturedTexts(); nmin = 5;
} nsec = 6;
bool ok[4]; parts = rx.capturedTexts();
const int day = parts[nday].toInt(&ok[0]); }
int year = parts[nyear].toInt(&ok[1]);
const int hour = parts[nhour].toInt(&ok[2]); bool ok[4];
const int minute = parts[nmin].toInt(&ok[3]); const int day = parts[nday].toInt(&ok[0]);
if (!ok[0] || !ok[1] || !ok[2] || !ok[3]) int year = parts[nyear].toInt(&ok[1]);
return QDateTime::currentDateTime(); const int hour = parts[nhour].toInt(&ok[2]);
int second = 0; const int minute = parts[nmin].toInt(&ok[3]);
if (!parts[nsec].isEmpty()) { if (!ok[0] || !ok[1] || !ok[2] || !ok[3])
second = parts[nsec].toInt(&ok[0]); return QDateTime::currentDateTime();
if (!ok[0])
return QDateTime::currentDateTime(); int second = 0;
} if (!parts[nsec].isEmpty()) {
bool leapSecond = (second == 60); second = parts[nsec].toInt(&ok[0]);
if (leapSecond) if (!ok[0])
second = 59; // apparently a leap second - validate below, once time zone is known return QDateTime::currentDateTime();
int month = 0; }
for ( ; month < 12 && parts[nmonth] != shortMonth[month]; ++month) ;
int dayOfWeek = -1; bool leapSecond = (second == 60);
if (!parts[nwday].isEmpty()) { if (leapSecond)
// Look up the weekday name second = 59; // apparently a leap second - validate below, once time zone is known
while (++dayOfWeek < 7 && shortDay[dayOfWeek] != parts[nwday]) ; int month = 0;
if (dayOfWeek >= 7) for ( ; (month < 12) && (parts[nmonth] != shortMonth[month]); ++month);
for (dayOfWeek = 0; dayOfWeek < 7 && longDay[dayOfWeek] != parts[nwday]; ++dayOfWeek) ; int dayOfWeek = -1;
} if (!parts[nwday].isEmpty()) {
// if (month >= 12 || dayOfWeek >= 7 // Look up the weekday name
// || (dayOfWeek < 0 && format == RFCDateDay)) while (++dayOfWeek < 7 && (shortDay[dayOfWeek] != parts[nwday]));
// return QDateTime; if (dayOfWeek >= 7)
int i = parts[nyear].size(); for (dayOfWeek = 0; dayOfWeek < 7 && (longDay[dayOfWeek] != parts[nwday]); ++dayOfWeek);
if (i < 4) { }
// It's an obsolete year specification with less than 4 digits
year += (i == 2 && year < 50) ? 2000: 1900; // if (month >= 12 || dayOfWeek >= 7
} // || (dayOfWeek < 0 && format == RFCDateDay))
// return QDateTime;
// Parse the UTC offset part int i = parts[nyear].size();
int offset = 0; // set default to '-0000' if (i < 4) {
bool negOffset = false; // It's an obsolete year specification with less than 4 digits
if (parts.count() > 10) { year += (i == 2 && year < 50) ? 2000 : 1900;
rx = QRegExp("^([+-])(\\d\\d)(\\d\\d)$"); }
if (!parts[10].indexOf(rx)) {
// It's a UTC offset ±hhmm // Parse the UTC offset part
parts = rx.capturedTexts(); int offset = 0; // set default to '-0000'
offset = parts[2].toInt(&ok[0]) * 3600; bool negOffset = false;
int offsetMin = parts[3].toInt(&ok[1]); if (parts.count() > 10) {
if (!ok[0] || !ok[1] || offsetMin > 59) rx = QRegExp("^([+-])(\\d\\d)(\\d\\d)$");
return QDateTime(); if (!parts[10].indexOf(rx)) {
offset += offsetMin * 60; // It's a UTC offset ±hhmm
negOffset = (parts[1] == QLatin1String("-")); parts = rx.capturedTexts();
if (negOffset) offset = parts[2].toInt(&ok[0]) * 3600;
offset = -offset; int offsetMin = parts[3].toInt(&ok[1]);
} else { if (!ok[0] || !ok[1] || offsetMin > 59)
// Check for an obsolete time zone name return QDateTime();
QByteArray zone = parts[10].toLatin1(); offset += offsetMin * 60;
if (zone.length() == 1 && isalpha(zone[0]) && toupper(zone[0]) != 'J') negOffset = (parts[1] == QLatin1String("-"));
negOffset = true; // military zone: RFC 2822 treats as '-0000' if (negOffset)
else if (zone != "UT" && zone != "GMT") { // treated as '+0000' offset = -offset;
offset = (zone == "EDT") ? -4*3600 }
: (zone == "EST" || zone == "CDT") ? -5*3600 else {
: (zone == "CST" || zone == "MDT") ? -6*3600 // Check for an obsolete time zone name
: (zone == "MST" || zone == "PDT") ? -7*3600 QByteArray zone = parts[10].toLatin1();
: (zone == "PST") ? -8*3600 if (zone.length() == 1 && isalpha(zone[0]) && toupper(zone[0]) != 'J') {
: 0; negOffset = true; // military zone: RFC 2822 treats as '-0000'
if (!offset) { }
// Check for any other alphabetic time zone else if (zone != "UT" && zone != "GMT") { // treated as '+0000'
bool nonalpha = false; offset = (zone == "EDT")
for (int i = 0, end = zone.size(); i < end && !nonalpha; ++i) ? -4 * 3600
nonalpha = !isalpha(zone[i]); : ((zone == "EST") || (zone == "CDT"))
if (nonalpha) ? -5 * 3600
return QDateTime(); : ((zone == "CST") || (zone == "MDT"))
// TODO: Attempt to recognize the time zone abbreviation? ? -6 * 3600
negOffset = true; // unknown time zone: RFC 2822 treats as '-0000' : (zone == "MST" || zone == "PDT")
? -7 * 3600
: (zone == "PST")
? -8 * 3600
: 0;
if (!offset) {
// Check for any other alphabetic time zone
bool nonalpha = false;
for (int i = 0, end = zone.size(); (i < end) && !nonalpha; ++i)
nonalpha = !isalpha(zone[i]);
if (nonalpha)
return QDateTime();
// TODO: Attempt to recognize the time zone abbreviation?
negOffset = true; // unknown time zone: RFC 2822 treats as '-0000'
}
}
} }
}
} }
}
QDate qdate(year, month+1, day); // convert date, and check for out-of-range QDate qdate(year, month + 1, day); // convert date, and check for out-of-range
if (!qdate.isValid()) if (!qdate.isValid())
return QDateTime::currentDateTime(); return QDateTime::currentDateTime();
QTime qTime(hour, minute, second);
QTime qTime(hour, minute, second);
QDateTime result(qdate, qTime, Qt::UTC); QDateTime result(qdate, qTime, Qt::UTC);
if (offset) if (offset)
result = result.addSecs(-offset); result = result.addSecs(-offset);
if (!result.isValid()) if (!result.isValid())
return QDateTime::currentDateTime(); // invalid date/time return QDateTime::currentDateTime(); // invalid date/time
if (leapSecond) { if (leapSecond) {
// Validate a leap second time. Leap seconds are inserted after 23:59:59 UTC. // Validate a leap second time. Leap seconds are inserted after 23:59:59 UTC.
// Convert the time to UTC and check that it is 00:00:00. // Convert the time to UTC and check that it is 00:00:00.
if ((hour*3600 + minute*60 + 60 - offset + 86400*5) % 86400) // (max abs(offset) is 100 hours) if ((hour*3600 + minute*60 + 60 - offset + 86400*5) % 86400) // (max abs(offset) is 100 hours)
return QDateTime::currentDateTime(); // the time isn't the last second of the day return QDateTime::currentDateTime(); // the time isn't the last second of the day
} }
return result;
return result;
} }
RssParser::RssParser(QObject *parent) : RssParser::RssParser(QObject *parent)
QThread(parent), m_running(true) : QThread(parent)
, m_running(true)
{ {
start(); start();
} }
RssParser::~RssParser() RssParser::~RssParser()
{ {
m_running = false; m_running = false;
m_waitCondition.wakeOne(); m_waitCondition.wakeOne();
wait(); wait();
} }
void RssParser::parseRssFile(const QString& feedUrl, const QString& filePath) void RssParser::parseRssFile(const QString &feedUrl, const QString &filePath)
{ {
qDebug() << Q_FUNC_INFO << feedUrl << filePath; qDebug() << Q_FUNC_INFO << feedUrl << filePath;
m_mutex.lock(); m_mutex.lock();
ParsingJob job = { feedUrl, Utils::Fs::fromNativePath(filePath) }; ParsingJob job = { feedUrl, Utils::Fs::fromNativePath(filePath) };
m_queue.enqueue(job); m_queue.enqueue(job);
// Wake up thread. // Wake up thread.
if (m_queue.count() == 1) { if (m_queue.count() == 1) {
qDebug() << Q_FUNC_INFO << "Waking up thread"; qDebug() << Q_FUNC_INFO << "Waking up thread";
m_waitCondition.wakeOne(); m_waitCondition.wakeOne();
} }
m_mutex.unlock(); m_mutex.unlock();
} }
void RssParser::clearFeedData(const QString &feedUrl) void RssParser::clearFeedData(const QString &feedUrl)
{ {
m_mutex.lock(); m_mutex.lock();
m_lastBuildDates.remove(feedUrl); m_lastBuildDates.remove(feedUrl);
m_mutex.unlock(); m_mutex.unlock();
} }
void RssParser::run() void RssParser::run()
{ {
while (m_running) { while (m_running) {
m_mutex.lock(); m_mutex.lock();
if (!m_queue.empty()) { if (!m_queue.empty()) {
ParsingJob job = m_queue.dequeue(); ParsingJob job = m_queue.dequeue();
m_mutex.unlock(); m_mutex.unlock();
parseFeed(job); parseFeed(job);
} else { }
qDebug() << Q_FUNC_INFO << "Thread is waiting."; else {
m_waitCondition.wait(&m_mutex); qDebug() << Q_FUNC_INFO << "Thread is waiting.";
qDebug() << Q_FUNC_INFO << "Thread woke up."; m_waitCondition.wait(&m_mutex);
m_mutex.unlock(); qDebug() << Q_FUNC_INFO << "Thread woke up.";
m_mutex.unlock();
}
} }
}
} }
void RssParser::parseRssArticle(QXmlStreamReader& xml, const QString& feedUrl) void RssParser::parseRssArticle(QXmlStreamReader &xml, const QString &feedUrl)
{ {
QVariantHash article; QVariantHash article;
while(!xml.atEnd()) { while(!xml.atEnd()) {
xml.readNext(); xml.readNext();
if(xml.isEndElement() && xml.name() == "item") if(xml.isEndElement() && xml.name() == "item")
break; break;
if (xml.isStartElement()) { if (xml.isStartElement()) {
if (xml.name() == "title") if (xml.name() == "title") {
article["title"] = xml.readElementText().trimmed(); article["title"] = xml.readElementText().trimmed();
else if (xml.name() == "enclosure") { }
if (xml.attributes().value("type") == "application/x-bittorrent") else if (xml.name() == "enclosure") {
article["torrent_url"] = xml.attributes().value("url").toString(); if (xml.attributes().value("type") == "application/x-bittorrent")
} article["torrent_url"] = xml.attributes().value("url").toString();
else if (xml.name() == "link") { }
QString link = xml.readElementText().trimmed(); else if (xml.name() == "link") {
if (link.startsWith("magnet:", Qt::CaseInsensitive)) QString link = xml.readElementText().trimmed();
article["torrent_url"] = link; // magnet link instead of a news URL if (link.startsWith("magnet:", Qt::CaseInsensitive))
else article["torrent_url"] = link; // magnet link instead of a news URL
article["news_link"] = link; else
} article["news_link"] = link;
else if (xml.name() == "description") }
article["description"] = xml.readElementText().trimmed(); else if (xml.name() == "description") {
else if (xml.name() == "pubDate") article["description"] = xml.readElementText().trimmed();
article["date"] = parseDate(xml.readElementText().trimmed()); }
else if (xml.name() == "author") else if (xml.name() == "pubDate") {
article["author"] = xml.readElementText().trimmed(); article["date"] = parseDate(xml.readElementText().trimmed());
else if (xml.name() == "guid") }
article["id"] = xml.readElementText().trimmed(); else if (xml.name() == "author") {
article["author"] = xml.readElementText().trimmed();
}
else if (xml.name() == "guid") {
article["id"] = xml.readElementText().trimmed();
}
}
} }
}
if (!article.contains("torrent_url") && article.contains("news_link")) if (!article.contains("torrent_url") && article.contains("news_link"))
article["torrent_url"] = article["news_link"]; article["torrent_url"] = article["news_link"];
if (!article.contains("id")) { if (!article.contains("id")) {
// Item does not have a guid, fall back to some other identifier // Item does not have a guid, fall back to some other identifier
const QString link = article.value("news_link").toString(); const QString link = article.value("news_link").toString();
if (!link.isEmpty()) if (!link.isEmpty()) {
article["id"] = link; article["id"] = link;
else { }
const QString title = article.value("title").toString(); else {
if (!title.isEmpty()) const QString title = article.value("title").toString();
article["id"] = title; if (!title.isEmpty()) {
else { article["id"] = title;
qWarning() << "Item has no guid, link or title, ignoring it..."; }
return; else {
} qWarning() << "Item has no guid, link or title, ignoring it...";
return;
}
}
} }
}
emit newArticle(feedUrl, article); emit newArticle(feedUrl, article);
} }
void RssParser::parseRSSChannel(QXmlStreamReader& xml, const QString& feedUrl) void RssParser::parseRSSChannel(QXmlStreamReader &xml, const QString &feedUrl)
{ {
qDebug() << Q_FUNC_INFO << feedUrl; qDebug() << Q_FUNC_INFO << feedUrl;
Q_ASSERT(xml.isStartElement() && xml.name() == "channel"); Q_ASSERT(xml.isStartElement() && xml.name() == "channel");
while(!xml.atEnd()) { while(!xml.atEnd()) {
xml.readNext(); xml.readNext();
if (xml.isStartElement()) { if (xml.isStartElement()) {
if (xml.name() == "title") { if (xml.name() == "title") {
QString title = xml.readElementText(); QString title = xml.readElementText();
emit feedTitle(feedUrl, title); emit feedTitle(feedUrl, title);
} }
else if (xml.name() == "lastBuildDate") { else if (xml.name() == "lastBuildDate") {
QString lastBuildDate = xml.readElementText(); QString lastBuildDate = xml.readElementText();
if (!lastBuildDate.isEmpty()) { if (!lastBuildDate.isEmpty()) {
QMutexLocker locker(&m_mutex); QMutexLocker locker(&m_mutex);
if (m_lastBuildDates.value(feedUrl, "") == lastBuildDate) { if (m_lastBuildDates.value(feedUrl, "") == lastBuildDate) {
qDebug() << "The RSS feed has not changed since last time, aborting parsing."; qDebug() << "The RSS feed has not changed since last time, aborting parsing.";
return; return;
} }
m_lastBuildDates[feedUrl] = lastBuildDate; m_lastBuildDates[feedUrl] = lastBuildDate;
}
}
else if (xml.name() == "item") {
parseRssArticle(xml, feedUrl);
}
} }
}
else if (xml.name() == "item") {
parseRssArticle(xml, feedUrl);
}
} }
}
} }
void RssParser::parseAtomArticle(QXmlStreamReader& xml, const QString& feedUrl, const QString& baseUrl) void RssParser::parseAtomArticle(QXmlStreamReader &xml, const QString &feedUrl, const QString &baseUrl)
{ {
QVariantHash article; QVariantHash article;
bool double_content = false; bool doubleContent = false;
while(!xml.atEnd()) {
xml.readNext();
if(xml.isEndElement() && xml.name() == "entry")
break;
if (xml.isStartElement()) {
if (xml.name() == "title") {
article["title"] = xml.readElementText().trimmed();
}
else if (xml.name() == "link") {
QString link = ( xml.attributes().isEmpty() ?
xml.readElementText().trimmed() :
xml.attributes().value("href").toString() );
if (link.startsWith("magnet:", Qt::CaseInsensitive))
article["torrent_url"] = link; // magnet link instead of a news URL
else
// Atom feeds can have relative links, work around this and
// take the stress of figuring article full URI from UI
// Assemble full URI
article["news_link"] = ( baseUrl.isEmpty() ? link : baseUrl + link );
}
else if (xml.name() == "summary" || xml.name() == "content"){
if(double_content) { // Duplicate content -> ignore
xml.readNext();
while(xml.name() != "summary" && xml.name() != "content")
xml.readNext();
continue;
}
// Try to also parse broken articles, which don't use html '&' escapes while(!xml.atEnd()) {
// Actually works great for non-broken content too
QString feedText = xml.readElementText(QXmlStreamReader::IncludeChildElements);
if (!feedText.isEmpty())
article["description"] = feedText.trimmed();
double_content = true;
}
else if (xml.name() == "updated"){
// ATOM uses standard compliant date, don't do fancy stuff
QDateTime articleDate = QDateTime::fromString(xml.readElementText().trimmed(), Qt::ISODate);
article["date"] = ( articleDate.isValid() ?
articleDate :
QDateTime::currentDateTime() );
}
else if (xml.name() == "author") {
xml.readNext(); xml.readNext();
while(xml.name() != "author") {
if(xml.name() == "name") if(xml.isEndElement() && (xml.name() == "entry"))
article["author"] = xml.readElementText().trimmed(); break;
xml.readNext();
if (xml.isStartElement()) {
if (xml.name() == "title") {
article["title"] = xml.readElementText().trimmed();
}
else if (xml.name() == "link") {
QString link = ( xml.attributes().isEmpty() ?
xml.readElementText().trimmed() :
xml.attributes().value("href").toString() );
if (link.startsWith("magnet:", Qt::CaseInsensitive))
article["torrent_url"] = link; // magnet link instead of a news URL
else
// Atom feeds can have relative links, work around this and
// take the stress of figuring article full URI from UI
// Assemble full URI
article["news_link"] = ( baseUrl.isEmpty() ? link : baseUrl + link );
}
else if ((xml.name() == "summary") || (xml.name() == "content")){
if (doubleContent) { // Duplicate content -> ignore
xml.readNext();
while ((xml.name() != "summary") && (xml.name() != "content"))
xml.readNext();
continue;
}
// Try to also parse broken articles, which don't use html '&' escapes
// Actually works great for non-broken content too
QString feedText = xml.readElementText(QXmlStreamReader::IncludeChildElements);
if (!feedText.isEmpty())
article["description"] = feedText.trimmed();
doubleContent = true;
}
else if (xml.name() == "updated") {
// ATOM uses standard compliant date, don't do fancy stuff
QDateTime articleDate = QDateTime::fromString(xml.readElementText().trimmed(), Qt::ISODate);
article["date"] = (articleDate.isValid() ? articleDate : QDateTime::currentDateTime());
}
else if (xml.name() == "author") {
xml.readNext();
while(xml.name() != "author") {
if(xml.name() == "name")
article["author"] = xml.readElementText().trimmed();
xml.readNext();
}
}
else if (xml.name() == "id") {
article["id"] = xml.readElementText().trimmed();
}
} }
}
else if (xml.name() == "id")
article["id"] = xml.readElementText().trimmed();
} }
}
if (!article.contains("torrent_url") && article.contains("news_link")) if (!article.contains("torrent_url") && article.contains("news_link"))
article["torrent_url"] = article["news_link"]; article["torrent_url"] = article["news_link"];
if (!article.contains("id")) { if (!article.contains("id")) {
// Item does not have a guid, fall back to some other identifier // Item does not have a guid, fall back to some other identifier
const QString link = article.value("news_link").toString(); const QString link = article.value("news_link").toString();
if (!link.isEmpty()) if (!link.isEmpty()) {
article["id"] = link; article["id"] = link;
else { }
const QString title = article.value("title").toString(); else {
if (!title.isEmpty()) const QString title = article.value("title").toString();
article["id"] = title; if (!title.isEmpty()) {
else { article["id"] = title;
qWarning() << "Item has no guid, link or title, ignoring it..."; }
return; else {
} qWarning() << "Item has no guid, link or title, ignoring it...";
return;
}
}
} }
}
emit newArticle(feedUrl, article); emit newArticle(feedUrl, article);
} }
void RssParser::parseAtomChannel(QXmlStreamReader& xml, const QString& feedUrl) void RssParser::parseAtomChannel(QXmlStreamReader &xml, const QString &feedUrl)
{ {
qDebug() << Q_FUNC_INFO << feedUrl; qDebug() << Q_FUNC_INFO << feedUrl;
Q_ASSERT(xml.isStartElement() && xml.name() == "feed"); Q_ASSERT(xml.isStartElement() && xml.name() == "feed");
QString baseURL = xml.attributes().value("xml:base").toString(); QString baseURL = xml.attributes().value("xml:base").toString();
while(!xml.atEnd()) { while (!xml.atEnd()) {
xml.readNext(); xml.readNext();
if (xml.isStartElement()) { if (xml.isStartElement()) {
if (xml.name() == "title") { if (xml.name() == "title") {
QString title = xml.readElementText(); QString title = xml.readElementText();
emit feedTitle(feedUrl, title); emit feedTitle(feedUrl, title);
} }
else if (xml.name() == "updated") { else if (xml.name() == "updated") {
QString lastBuildDate = xml.readElementText(); QString lastBuildDate = xml.readElementText();
if (!lastBuildDate.isEmpty()) { if (!lastBuildDate.isEmpty()) {
QMutexLocker locker(&m_mutex); QMutexLocker locker(&m_mutex);
if (m_lastBuildDates.value(feedUrl) == lastBuildDate) { if (m_lastBuildDates.value(feedUrl) == lastBuildDate) {
qDebug() << "The RSS feed has not changed since last time, aborting parsing."; qDebug() << "The RSS feed has not changed since last time, aborting parsing.";
return; return;
} }
m_lastBuildDates[feedUrl] = lastBuildDate; m_lastBuildDates[feedUrl] = lastBuildDate;
}
}
else if (xml.name() == "entry") {
parseAtomArticle(xml, feedUrl, baseURL);
}
} }
}
else if (xml.name() == "entry") {
parseAtomArticle(xml, feedUrl, baseURL);
}
} }
}
} }
// read and create items from a rss document // read and create items from a rss document
void RssParser::parseFeed(const ParsingJob& job) void RssParser::parseFeed(const ParsingJob &job)
{ {
qDebug() << Q_FUNC_INFO << job.feedUrl << job.filePath; qDebug() << Q_FUNC_INFO << job.feedUrl << job.filePath;
QFile fileRss(job.filePath); QFile fileRss(job.filePath);
if (!fileRss.open(QIODevice::ReadOnly | QIODevice::Text)) { if (!fileRss.open(QIODevice::ReadOnly | QIODevice::Text)) {
reportFailure(job, tr("Failed to open downloaded RSS file.")); reportFailure(job, tr("Failed to open downloaded RSS file."));
return; return;
} }
QXmlStreamReader xml(&fileRss);
QXmlStreamReader xml(&fileRss);
bool found_channel = false; bool foundChannel = false;
while (xml.readNextStartElement()) { while (xml.readNextStartElement()) {
if (xml.name() == "rss") { if (xml.name() == "rss") {
// Find channels // Find channels
while (xml.readNextStartElement()) { while (xml.readNextStartElement()) {
if (xml.name() == "channel") { if (xml.name() == "channel") {
parseRSSChannel(xml, job.feedUrl); parseRSSChannel(xml, job.feedUrl);
found_channel = true; foundChannel = true;
break; break;
} else { }
qDebug() << "Skip rss item: " << xml.name(); else {
xml.skipCurrentElement(); qDebug() << "Skip rss item: " << xml.name();
xml.skipCurrentElement();
}
}
break;
}
else if (xml.name() == "feed") { // Atom feed
parseAtomChannel(xml, job.feedUrl);
foundChannel = true;
break;
}
else {
qDebug() << "Skip root item: " << xml.name();
xml.skipCurrentElement();
} }
}
break;
} }
else if (xml.name() == "feed") { // Atom feed
parseAtomChannel(xml, job.feedUrl); if (xml.hasError()) {
found_channel = true; reportFailure(job, xml.errorString());
break; return;
} else {
qDebug() << "Skip root item: " << xml.name();
xml.skipCurrentElement();
} }
}
if (!foundChannel) {
if (xml.hasError()) { reportFailure(job, tr("Invalid RSS feed at '%1'.").arg(job.feedUrl));
reportFailure(job, xml.errorString()); return;
return; }
}
// Clean up
if (!found_channel) { fileRss.close();
reportFailure(job, tr("Invalid RSS feed at '%1'.").arg(job.feedUrl)); emit feedParsingFinished(job.feedUrl, QString());
return; Utils::Fs::forceRemove(job.filePath);
}
// Clean up
fileRss.close();
emit feedParsingFinished(job.feedUrl, QString());
Utils::Fs::forceRemove(job.filePath);
} }
void RssParser::reportFailure(const ParsingJob& job, const QString& error) void RssParser::reportFailure(const ParsingJob &job, const QString &error)
{ {
emit feedParsingFinished(job.feedUrl, error); emit feedParsingFinished(job.feedUrl, error);
Utils::Fs::forceRemove(job.filePath); Utils::Fs::forceRemove(job.filePath);
} }

47
src/base/rss/rssparser.h

@ -1,5 +1,5 @@
/* /*
* Bittorrent Client using Qt4 and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2012 Christophe Dumez * Copyright (C) 2012 Christophe Dumez
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -40,39 +40,40 @@
struct ParsingJob; struct ParsingJob;
class RssParser : public QThread class RssParser: public QThread
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit RssParser(QObject *parent = 0); explicit RssParser(QObject *parent = 0);
virtual ~RssParser(); virtual ~RssParser();
signals: signals:
void newArticle(const QString& feedUrl, const QVariantHash& rssArticle); void newArticle(const QString &feedUrl, const QVariantHash &rssArticle);
void feedTitle(const QString& feedUrl, const QString& title); void feedTitle(const QString &feedUrl, const QString &title);
void feedParsingFinished(const QString& feedUrl, const QString& error); void feedParsingFinished(const QString &feedUrl, const QString &error);
public slots: public slots:
void parseRssFile(const QString& feedUrl, const QString& filePath); void parseRssFile(const QString &feedUrl, const QString &filePath);
void clearFeedData(const QString& feedUrl); void clearFeedData(const QString &feedUrl);
protected: protected:
virtual void run(); virtual void run();
static QDateTime parseDate(const QString& string);
void parseRssArticle(QXmlStreamReader& xml, const QString& feedUrl);
void parseRSSChannel(QXmlStreamReader& xml, const QString& feedUrl);
void parseAtomArticle(QXmlStreamReader& xml, const QString& feedUrl, const QString& baseUrl);
void parseAtomChannel(QXmlStreamReader& xml, const QString& feedUrl);
void parseFeed(const ParsingJob& job);
void reportFailure(const ParsingJob& job, const QString& error);
private: private:
bool m_running; static QDateTime parseDate(const QString &string);
QMutex m_mutex; void parseRssArticle(QXmlStreamReader &xml, const QString &feedUrl);
QQueue<ParsingJob> m_queue; void parseRSSChannel(QXmlStreamReader &xml, const QString &feedUrl);
QWaitCondition m_waitCondition; void parseAtomArticle(QXmlStreamReader &xml, const QString &feedUrl, const QString &baseUrl);
QHash<QString/*feedUrl*/, QString/*lastBuildDate*/> m_lastBuildDates; // Optimization void parseAtomChannel(QXmlStreamReader &xml, const QString &feedUrl);
void parseFeed(const ParsingJob &job);
void reportFailure(const ParsingJob &job, const QString &error);
bool m_running;
QMutex m_mutex;
QQueue<ParsingJob> m_queue;
QWaitCondition m_waitCondition;
QHash<QString/*feedUrl*/, QString/*lastBuildDate*/> m_lastBuildDates; // Optimization
}; };
#endif // RSSPARSER_H #endif // RSSPARSER_H

4
src/gui/rss/feedlistwidget.cpp

@ -28,8 +28,8 @@
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org * Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
*/ */
#include "core/rss/rssmanager.h" #include "base/rss/rssmanager.h"
#include "core/rss/rssfeed.h" #include "base/rss/rssfeed.h"
#include "guiiconprovider.h" #include "guiiconprovider.h"
#include "feedlistwidget.h" #include "feedlistwidget.h"

Loading…
Cancel
Save