diff --git a/src/base/base.pri b/src/base/base.pri index 3ce02d04d..b38caa601 100644 --- a/src/base/base.pri +++ b/src/base/base.pri @@ -36,6 +36,14 @@ HEADERS += \ $$PWD/bittorrent/private/bandwidthscheduler.h \ $$PWD/bittorrent/private/filterparserthread.h \ $$PWD/bittorrent/private/statistics.h \ + $$PWD/rss/rssmanager.h \ + $$PWD/rss/rssfeed.h \ + $$PWD/rss/rssfolder.h \ + $$PWD/rss/rssfile.h \ + $$PWD/rss/rssarticle.h \ + $$PWD/rss/rssdownloadrule.h \ + $$PWD/rss/rssdownloadrulelist.h \ + $$PWD/rss/private/rssparser.h \ $$PWD/utils/fs.h \ $$PWD/utils/gzip.h \ $$PWD/utils/misc.h \ @@ -79,6 +87,14 @@ SOURCES += \ $$PWD/bittorrent/private/bandwidthscheduler.cpp \ $$PWD/bittorrent/private/filterparserthread.cpp \ $$PWD/bittorrent/private/statistics.cpp \ + $$PWD/rss/rssmanager.cpp \ + $$PWD/rss/rssfeed.cpp \ + $$PWD/rss/rssfolder.cpp \ + $$PWD/rss/rssarticle.cpp \ + $$PWD/rss/rssdownloadrule.cpp \ + $$PWD/rss/rssdownloadrulelist.cpp \ + $$PWD/rss/rssfile.cpp \ + $$PWD/rss/private/rssparser.cpp \ $$PWD/utils/fs.cpp \ $$PWD/utils/gzip.cpp \ $$PWD/utils/misc.cpp \ diff --git a/src/base/rss/private/rssparser.cpp b/src/base/rss/private/rssparser.cpp new file mode 100644 index 000000000..8dc1b0849 --- /dev/null +++ b/src/base/rss/private/rssparser.cpp @@ -0,0 +1,467 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2015 Vladimir Golovnev + * Copyright (C) 2012 Christophe Dumez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + * + * Contact : chris@qbittorrent.org + */ + +#include +#include +#include +#include +#include +#include + +#include "rssparser.h" + +namespace +{ + const char shortDay[][4] = { + "Mon", "Tue", "Wed", + "Thu", "Fri", "Sat", + "Sun" + }; + + const char longDay[][10] = { + "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday", + "Sunday" + }; + + const char shortMonth[][4] = { + "Jan", "Feb", "Mar", "Apr", + "May", "Jun", "Jul", "Aug", + "Sep", "Oct", "Nov", "Dec" + }; + + // Ported to Qt from KDElibs4 + QDateTime parseDate(const QString &string) + { + const QString str = string.trimmed(); + if (str.isEmpty()) + return QDateTime::currentDateTime(); + + int nyear = 6; // indexes within string to values + int nmonth = 4; + int nday = 2; + int nwday = 1; + int nhour = 7; + int nmin = 8; + int nsec = 9; + // Also accept obsolete form "Weekday, DD-Mon-YY HH:MM:SS ±hhmm" + 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+)$"); + QStringList parts; + if (!str.indexOf(rx)) { + // Check that if date has '-' separators, both separators are '-'. + parts = rx.capturedTexts(); + bool h1 = (parts[3] == QLatin1String("-")); + bool h2 = (parts[5] == QLatin1String("-")); + if (h1 != h2) + return QDateTime::currentDateTime(); + } + else { + // 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)$"); + if (str.indexOf(rx)) + return QDateTime::currentDateTime(); + nyear = 7; + nmonth = 2; + nday = 3; + nwday = 1; + nhour = 4; + nmin = 5; + nsec = 6; + parts = rx.capturedTexts(); + } + + bool ok[4]; + const int day = parts[nday].toInt(&ok[0]); + int year = parts[nyear].toInt(&ok[1]); + const int hour = parts[nhour].toInt(&ok[2]); + const int minute = parts[nmin].toInt(&ok[3]); + if (!ok[0] || !ok[1] || !ok[2] || !ok[3]) + return QDateTime::currentDateTime(); + + int second = 0; + if (!parts[nsec].isEmpty()) { + second = parts[nsec].toInt(&ok[0]); + if (!ok[0]) + return QDateTime::currentDateTime(); + } + + bool leapSecond = (second == 60); + if (leapSecond) + second = 59; // apparently a leap second - validate below, once time zone is known + int month = 0; + for ( ; (month < 12) && (parts[nmonth] != shortMonth[month]); ++month); + int dayOfWeek = -1; + if (!parts[nwday].isEmpty()) { + // Look up the weekday name + while (++dayOfWeek < 7 && (shortDay[dayOfWeek] != parts[nwday])); + if (dayOfWeek >= 7) + for (dayOfWeek = 0; dayOfWeek < 7 && (longDay[dayOfWeek] != parts[nwday]); ++dayOfWeek); + } + + // if (month >= 12 || dayOfWeek >= 7 + // || (dayOfWeek < 0 && format == RFCDateDay)) + // return QDateTime; + int i = parts[nyear].size(); + if (i < 4) { + // It's an obsolete year specification with less than 4 digits + year += (i == 2 && year < 50) ? 2000 : 1900; + } + + // Parse the UTC offset part + int offset = 0; // set default to '-0000' + bool negOffset = false; + if (parts.count() > 10) { + rx = QRegExp("^([+-])(\\d\\d)(\\d\\d)$"); + if (!parts[10].indexOf(rx)) { + // It's a UTC offset ±hhmm + parts = rx.capturedTexts(); + offset = parts[2].toInt(&ok[0]) * 3600; + int offsetMin = parts[3].toInt(&ok[1]); + if (!ok[0] || !ok[1] || offsetMin > 59) + return QDateTime(); + offset += offsetMin * 60; + negOffset = (parts[1] == QLatin1String("-")); + if (negOffset) + offset = -offset; + } + else { + // Check for an obsolete time zone name + QByteArray zone = parts[10].toLatin1(); + if (zone.length() == 1 && isalpha(zone[0]) && toupper(zone[0]) != 'J') { + negOffset = true; // military zone: RFC 2822 treats as '-0000' + } + else if (zone != "UT" && zone != "GMT") { // treated as '+0000' + offset = (zone == "EDT") + ? -4 * 3600 + : ((zone == "EST") || (zone == "CDT")) + ? -5 * 3600 + : ((zone == "CST") || (zone == "MDT")) + ? -6 * 3600 + : (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 + if (!qdate.isValid()) + return QDateTime::currentDateTime(); + + QTime qTime(hour, minute, second); + QDateTime result(qdate, qTime, Qt::UTC); + if (offset) + result = result.addSecs(-offset); + if (!result.isValid()) + return QDateTime::currentDateTime(); // invalid date/time + + if (leapSecond) { + // 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. + 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 result; + } +} + +using namespace Rss::Private; + +// read and create items from a rss document +void Parser::parse(const QByteArray &feedData) +{ + qDebug() << Q_FUNC_INFO; + + QXmlStreamReader xml(feedData); + bool foundChannel = false; + while (xml.readNextStartElement()) { + if (xml.name() == "rss") { + // Find channels + while (xml.readNextStartElement()) { + if (xml.name() == "channel") { + parseRSSChannel(xml); + foundChannel = true; + break; + } + else { + qDebug() << "Skip rss item: " << xml.name(); + xml.skipCurrentElement(); + } + } + break; + } + else if (xml.name() == "feed") { // Atom feed + parseAtomChannel(xml); + foundChannel = true; + break; + } + else { + qDebug() << "Skip root item: " << xml.name(); + xml.skipCurrentElement(); + } + } + + if (xml.hasError()) + emit finished(xml.errorString()); + else if (!foundChannel) + emit finished(tr("Invalid RSS feed.")); + else + emit finished(QString()); +} + +void Parser::parseRssArticle(QXmlStreamReader &xml) +{ + QVariantHash article; + + while(!xml.atEnd()) { + xml.readNext(); + + if(xml.isEndElement() && xml.name() == "item") + break; + + if (xml.isStartElement()) { + if (xml.name() == "title") { + article["title"] = xml.readElementText().trimmed(); + } + else if (xml.name() == "enclosure") { + 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(); + if (link.startsWith("magnet:", Qt::CaseInsensitive)) + article["torrent_url"] = link; // magnet link instead of a news URL + else + article["news_link"] = link; + } + else if (xml.name() == "description") { + article["description"] = xml.readElementText().trimmed(); + } + else if (xml.name() == "pubDate") { + article["date"] = parseDate(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")) + article["torrent_url"] = article["news_link"]; + + if (!article.contains("id")) { + // Item does not have a guid, fall back to some other identifier + const QString link = article.value("news_link").toString(); + if (!link.isEmpty()) { + article["id"] = link; + } + else { + const QString title = article.value("title").toString(); + if (!title.isEmpty()) { + article["id"] = title; + } + else { + qWarning() << "Item has no guid, link or title, ignoring it..."; + return; + } + } + } + + emit newArticle(article); +} + +void Parser::parseRSSChannel(QXmlStreamReader &xml) +{ + qDebug() << Q_FUNC_INFO; + Q_ASSERT(xml.isStartElement() && xml.name() == "channel"); + + while(!xml.atEnd()) { + xml.readNext(); + + if (xml.isStartElement()) { + if (xml.name() == "title") { + QString title = xml.readElementText(); + emit feedTitle(title); + } + else if (xml.name() == "lastBuildDate") { + QString lastBuildDate = xml.readElementText(); + if (!lastBuildDate.isEmpty()) { + if (m_lastBuildDate == lastBuildDate) { + qDebug() << "The RSS feed has not changed since last time, aborting parsing."; + return; + } + m_lastBuildDate = lastBuildDate; + } + } + else if (xml.name() == "item") { + parseRssArticle(xml); + } + } + } +} + +void Parser::parseAtomArticle(QXmlStreamReader &xml) +{ + QVariantHash article; + 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"] = ( m_baseUrl.isEmpty() ? link : m_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(); + } + } + } + + if (!article.contains("torrent_url") && article.contains("news_link")) + article["torrent_url"] = article["news_link"]; + + if (!article.contains("id")) { + // Item does not have a guid, fall back to some other identifier + const QString link = article.value("news_link").toString(); + if (!link.isEmpty()) { + article["id"] = link; + } + else { + const QString title = article.value("title").toString(); + if (!title.isEmpty()) { + article["id"] = title; + } + else { + qWarning() << "Item has no guid, link or title, ignoring it..."; + return; + } + } + } + + emit newArticle(article); +} + +void Parser::parseAtomChannel(QXmlStreamReader &xml) +{ + qDebug() << Q_FUNC_INFO; + Q_ASSERT(xml.isStartElement() && xml.name() == "feed"); + + m_baseUrl = xml.attributes().value("xml:base").toString(); + + while (!xml.atEnd()) { + xml.readNext(); + + if (xml.isStartElement()) { + if (xml.name() == "title") { + QString title = xml.readElementText(); + emit feedTitle(title); + } + else if (xml.name() == "updated") { + QString lastBuildDate = xml.readElementText(); + if (!lastBuildDate.isEmpty()) { + if (m_lastBuildDate == lastBuildDate) { + qDebug() << "The RSS feed has not changed since last time, aborting parsing."; + return; + } + m_lastBuildDate = lastBuildDate; + } + } + else if (xml.name() == "entry") { + parseAtomArticle(xml); + } + } + } +} diff --git a/src/gui/rss/rssparser.h b/src/base/rss/private/rssparser.h similarity index 50% rename from src/gui/rss/rssparser.h rename to src/base/rss/private/rssparser.h index bb3b2466f..afbf1df03 100644 --- a/src/gui/rss/rssparser.h +++ b/src/base/rss/private/rssparser.h @@ -1,6 +1,7 @@ /* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2012 Christophe Dumez + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2015 Vladimir Golovnev + * Copyright (C) 2012 Christophe Dumez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -31,47 +32,38 @@ #ifndef RSSPARSER_H #define RSSPARSER_H -#include "rssarticle.h" -#include -#include -#include -#include +#include +#include +#include -struct ParsingJob; +class QXmlStreamReader; -class RssParser : public QThread +namespace Rss { - Q_OBJECT + namespace Private + { + class Parser: public QObject + { + Q_OBJECT -public: - explicit RssParser(QObject *parent = 0); - virtual ~RssParser(); + public slots: + void parse(const QByteArray &feedData); -signals: - void newArticle(const QString& feedUrl, const QVariantHash& rssArticle); - void feedTitle(const QString& feedUrl, const QString& title); - void feedParsingFinished(const QString& feedUrl, const QString& error); + signals: + void newArticle(const QVariantHash &rssArticle); + void feedTitle(const QString &title); + void finished(const QString &error); -public slots: - void parseRssFile(const QString& feedUrl, const QString& filePath); - void clearFeedData(const QString& feedUrl); + private: + void parseRssArticle(QXmlStreamReader &xml); + void parseRSSChannel(QXmlStreamReader &xml); + void parseAtomArticle(QXmlStreamReader &xml); + void parseAtomChannel(QXmlStreamReader &xml); -protected: - 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: - bool m_running; - QMutex m_mutex; - QQueue m_queue; - QWaitCondition m_waitCondition; - QHash m_lastBuildDates; // Optimization -}; + QString m_lastBuildDate; // Optimization + QString m_baseUrl; + }; + } +} #endif // RSSPARSER_H diff --git a/src/base/rss/rssarticle.cpp b/src/base/rss/rssarticle.cpp new file mode 100644 index 000000000..1357d09c9 --- /dev/null +++ b/src/base/rss/rssarticle.cpp @@ -0,0 +1,143 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2010 Christophe Dumez + * Copyright (C) 2010 Arnaud Demaiziere + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + * + * Contact: chris@qbittorrent.org, arnaud@qbittorrent.org + */ + +#include +#include +#include + +#include "rssfeed.h" +#include "rssarticle.h" + +using namespace Rss; + +// public constructor +Article::Article(Feed *parent, const QString &guid) + : m_parent(parent) + , m_guid(guid) + , m_read(false) +{ +} + +bool Article::hasAttachment() const +{ + return !m_torrentUrl.isEmpty(); +} + +QVariantHash Article::toHash() const +{ + QVariantHash item; + item["title"] = m_title; + item["id"] = m_guid; + item["torrent_url"] = m_torrentUrl; + item["news_link"] = m_link; + item["description"] = m_description; + item["date"] = m_date; + item["author"] = m_author; + item["read"] = m_read; + return item; +} + +ArticlePtr Article::fromHash(Feed *parent, const QVariantHash &h) +{ + const QString guid = h.value("id").toString(); + if (guid.isEmpty()) + return ArticlePtr(); + + ArticlePtr art(new Article(parent, guid)); + art->m_title = h.value("title", "").toString(); + art->m_torrentUrl = h.value("torrent_url", "").toString(); + art->m_link = h.value("news_link", "").toString(); + art->m_description = h.value("description").toString(); + art->m_date = h.value("date").toDateTime(); + art->m_author = h.value("author").toString(); + art->m_read = h.value("read", false).toBool(); + + return art; +} + +Feed *Article::parent() const +{ + return m_parent; +} + +const QString &Article::author() const +{ + return m_author; +} + +const QString &Article::torrentUrl() const +{ + return m_torrentUrl; +} + +const QString &Article::link() const +{ + return m_link; +} + +QString Article::description() const +{ + return m_description.isNull() ? "" : m_description; +} + +const QDateTime &Article::date() const +{ + return m_date; +} + +bool Article::isRead() const +{ + return m_read; +} + +void Article::markAsRead() +{ + if (!m_read) { + m_read = true; + emit articleWasRead(); + } +} + +const QString &Article::guid() const +{ + return m_guid; +} + +const QString &Article::title() const +{ + return m_title; +} + +void Article::handleTorrentDownloadSuccess(const QString &url) +{ + if (url == m_torrentUrl) + markAsRead(); +} diff --git a/src/gui/rss/rssarticle.h b/src/base/rss/rssarticle.h similarity index 52% rename from src/gui/rss/rssarticle.h rename to src/base/rss/rssarticle.h index 7deeaf560..e81a10f6a 100644 --- a/src/gui/rss/rssarticle.h +++ b/src/base/rss/rssarticle.h @@ -1,6 +1,7 @@ /* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2010 Christophe Dumez + * Copyright (C) 2010 Arnaud Demaiziere * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -31,58 +32,60 @@ #ifndef RSSARTICLE_H #define RSSARTICLE_H -#include #include #include #include -class RssFeed; -class RssArticle; +namespace Rss +{ + class Feed; + class Article; -typedef QSharedPointer RssArticlePtr; + typedef QSharedPointer
ArticlePtr; -// Item of a rss stream, single information -class RssArticle : public QObject { - Q_OBJECT + // Item of a rss stream, single information + class Article: public QObject + { + Q_OBJECT -public: - RssArticle(RssFeed* parent, const QString& guid); - // Accessors - bool hasAttachment() const; - const QString& guid() const; - RssFeed* parent() const; - const QString& title() const; - const QString& author() const; - const QString& torrentUrl() const; - const QString& link() const; - QString description() const; - const QDateTime& date() const; - bool isRead() const; - // Setters - void markAsRead(); - // Serialization - QVariantHash toHash() const; + public: + Article(Feed *parent, const QString &guid); -signals: - void articleWasRead(); + // Accessors + bool hasAttachment() const; + const QString &guid() const; + Feed *parent() const; + const QString &title() const; + const QString &author() const; + const QString &torrentUrl() const; + const QString &link() const; + QString description() const; + const QDateTime &date() const; + bool isRead() const; + // Setters + void markAsRead(); -public slots: - void handleTorrentDownloadSuccess(const QString& url); + // Serialization + QVariantHash toHash() const; + static ArticlePtr fromHash(Feed *parent, const QVariantHash &hash); - friend RssArticlePtr hashToRssArticle(RssFeed* parent, const QVariantHash& hash); + signals: + void articleWasRead(); -private: - RssFeed* m_parent; - QString m_guid; - QString m_title; - QString m_torrentUrl; - QString m_link; - QString m_description; - QDateTime m_date; - QString m_author; - bool m_read; -}; + public slots: + void handleTorrentDownloadSuccess(const QString &url); -RssArticlePtr hashToRssArticle(RssFeed* parent, const QVariantHash& hash); + private: + Feed *m_parent; + QString m_guid; + QString m_title; + QString m_torrentUrl; + QString m_link; + QString m_description; + QDateTime m_date; + QString m_author; + bool m_read; + }; +} #endif // RSSARTICLE_H diff --git a/src/base/rss/rssdownloadrule.cpp b/src/base/rss/rssdownloadrule.cpp new file mode 100644 index 000000000..a7ef0bb2d --- /dev/null +++ b/src/base/rss/rssdownloadrule.cpp @@ -0,0 +1,311 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2010 Christophe Dumez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + * + * Contact : chris@qbittorrent.org + */ + +#include +#include +#include + +#include "base/preferences.h" +#include "base/utils/fs.h" +#include "rssfeed.h" +#include "rssarticle.h" +#include "rssdownloadrule.h" + +using namespace Rss; + +DownloadRule::DownloadRule() + : m_enabled(false) + , m_useRegex(false) + , m_apstate(USE_GLOBAL) +{ +} + +bool DownloadRule::matches(const QString &articleTitle) const +{ + foreach (const QString &token, m_mustContain) { + if (!token.isEmpty()) { + QRegExp reg(token, Qt::CaseInsensitive, m_useRegex ? QRegExp::RegExp : QRegExp::Wildcard); + if (reg.indexIn(articleTitle) < 0) + 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(articleTitle) > -1) + return false; + } + } + if (!m_episodeFilter.isEmpty()) { + qDebug("Checking episode filter"); + QRegExp f("(^\\d{1,4})x(.*;$)"); + int pos = f.indexIn(m_episodeFilter); + if (pos < 0) + 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(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; + } + return true; +} + +void DownloadRule::setMustContain(const QString &tokens) +{ + if (m_useRegex) + m_mustContain = QStringList() << tokens; + else + m_mustContain = tokens.split(" "); +} + +void DownloadRule::setMustNotContain(const QString &tokens) +{ + if (m_useRegex) + m_mustNotContain = QStringList() << tokens; + else + m_mustNotContain = tokens.split("|"); +} + +QStringList DownloadRule::rssFeeds() const +{ + return m_rssFeeds; +} + +void DownloadRule::setRssFeeds(const QStringList &rssFeeds) +{ + m_rssFeeds = rssFeeds; +} + +QString DownloadRule::name() const +{ + return m_name; +} + +void DownloadRule::setName(const QString &name) +{ + m_name = name; +} + +QString DownloadRule::savePath() const +{ + return m_savePath; +} + +DownloadRulePtr DownloadRule::fromVariantHash(const QVariantHash &ruleHash) +{ + DownloadRulePtr rule(new DownloadRule); + 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 DownloadRule::toVariantHash() const +{ + QVariantHash hash; + hash["name"] = m_name; + hash["must_contain"] = m_mustContain.join(" "); + hash["must_not_contain"] = m_mustNotContain.join("|"); + hash["save_path"] = m_savePath; + hash["affected_feeds"] = m_rssFeeds; + hash["enabled"] = m_enabled; + hash["label_assigned"] = m_label; + hash["use_regex"] = m_useRegex; + hash["add_paused"] = m_apstate; + hash["episode_filter"] = m_episodeFilter; + hash["last_match"] = m_lastMatch; + hash["ignore_days"] = m_ignoreDays; + return hash; +} + +bool DownloadRule::operator==(const DownloadRule &other) const +{ + return m_name == other.name(); +} + +void DownloadRule::setSavePath(const QString &savePath) +{ + if (!savePath.isEmpty() && (QDir(savePath) != QDir(Preferences::instance()->getSavePath()))) + m_savePath = Utils::Fs::fromNativePath(savePath); + else + m_savePath = QString(); +} + +DownloadRule::AddPausedState DownloadRule::addPaused() const +{ + return m_apstate; +} + +void DownloadRule::setAddPaused(const DownloadRule::AddPausedState &aps) +{ + m_apstate = aps; +} + +QString DownloadRule::label() const +{ + return m_label; +} + +void DownloadRule::setLabel(const QString &label) +{ + m_label = label; +} + +bool DownloadRule::isEnabled() const +{ + return m_enabled; +} + +void DownloadRule::setEnabled(bool enable) +{ + m_enabled = enable; +} + +void DownloadRule::setLastMatch(const QDateTime &d) +{ + m_lastMatch = d; +} + +QDateTime DownloadRule::lastMatch() const +{ + return m_lastMatch; +} + +void DownloadRule::setIgnoreDays(int d) +{ + m_ignoreDays = d; +} + +int DownloadRule::ignoreDays() const +{ + return m_ignoreDays; +} + +QString DownloadRule::mustContain() const +{ + return m_mustContain.join(" "); +} + +QString DownloadRule::mustNotContain() const +{ + return m_mustNotContain.join("|"); +} + +bool DownloadRule::useRegex() const +{ + return m_useRegex; +} + +void DownloadRule::setUseRegex(bool enabled) +{ + m_useRegex = enabled; +} + +QString DownloadRule::episodeFilter() const +{ + return m_episodeFilter; +} + +void DownloadRule::setEpisodeFilter(const QString &e) +{ + m_episodeFilter = e; +} + +QStringList DownloadRule::findMatchingArticles(const FeedPtr &feed) const +{ + QStringList ret; + const ArticleHash &feedArticles = feed->articleHash(); + + ArticleHash::ConstIterator artIt = feedArticles.begin(); + ArticleHash::ConstIterator artItend = feedArticles.end(); + for ( ; artIt != artItend ; ++artIt) { + const QString title = artIt.value()->title(); + if (matches(title)) + ret << title; + } + return ret; +} diff --git a/src/base/rss/rssdownloadrule.h b/src/base/rss/rssdownloadrule.h new file mode 100644 index 000000000..b22c14594 --- /dev/null +++ b/src/base/rss/rssdownloadrule.h @@ -0,0 +1,106 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2010 Christophe Dumez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + * + * Contact : chris@qbittorrent.org + */ + +#ifndef RSSDOWNLOADRULE_H +#define RSSDOWNLOADRULE_H + +#include +#include +#include +#include + +namespace Rss +{ + class Feed; + typedef QSharedPointer FeedPtr; + + class DownloadRule; + typedef QSharedPointer DownloadRulePtr; + + class DownloadRule + { + public: + enum AddPausedState + { + USE_GLOBAL = 0, + ALWAYS_PAUSED, + NEVER_PAUSED + }; + + DownloadRule(); + + static DownloadRulePtr fromVariantHash(const QVariantHash &ruleHash); + QVariantHash toVariantHash() const; + bool matches(const QString &articleTitle) const; + void setMustContain(const QString &tokens); + void setMustNotContain(const QString &tokens); + QStringList rssFeeds() const; + void setRssFeeds(const QStringList &rssFeeds); + QString name() const; + void setName(const QString &name); + QString savePath() const; + void setSavePath(const QString &savePath); + AddPausedState addPaused() const; + void setAddPaused(const AddPausedState &aps); + QString label() const; + void setLabel(const QString &label); + bool isEnabled() const; + void setEnabled(bool enable); + void setLastMatch(const QDateTime &d); + QDateTime lastMatch() const; + void setIgnoreDays(int d); + int ignoreDays() const; + QString mustContain() const; + QString mustNotContain() const; + bool useRegex() const; + void setUseRegex(bool enabled); + QString episodeFilter() const; + void setEpisodeFilter(const QString &e); + QStringList findMatchingArticles(const FeedPtr &feed) const; + // Operators + bool operator==(const DownloadRule &other) const; + + private: + QString m_name; + QStringList m_mustContain; + QStringList m_mustNotContain; + QString m_episodeFilter; + QString m_savePath; + QString m_label; + bool m_enabled; + QStringList m_rssFeeds; + bool m_useRegex; + AddPausedState m_apstate; + QDateTime m_lastMatch; + int m_ignoreDays; + }; +} + +#endif // RSSDOWNLOADRULE_H diff --git a/src/base/rss/rssdownloadrulelist.cpp b/src/base/rss/rssdownloadrulelist.cpp new file mode 100644 index 000000000..55d52b2d8 --- /dev/null +++ b/src/base/rss/rssdownloadrulelist.cpp @@ -0,0 +1,185 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2010 Christophe Dumez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + * + * Contact : chris@qbittorrent.org + */ + +#include +#include +#include + +#include "base/preferences.h" +#include "base/qinisettings.h" +#include "rssdownloadrulelist.h" + +using namespace Rss; + +DownloadRuleList::DownloadRuleList() +{ + loadRulesFromStorage(); +} + +DownloadRulePtr DownloadRuleList::findMatchingRule(const QString &feedUrl, const QString &articleTitle) const +{ + Q_ASSERT(Preferences::instance()->isRssDownloadingEnabled()); + QStringList ruleNames = m_feedRules.value(feedUrl); + foreach (const QString &rule_name, ruleNames) { + DownloadRulePtr rule = m_rules[rule_name]; + if (rule->isEnabled() && rule->matches(articleTitle)) return rule; + } + return DownloadRulePtr(); +} + +void DownloadRuleList::replace(DownloadRuleList *other) +{ + m_rules.clear(); + m_feedRules.clear(); + foreach (const QString &name, other->ruleNames()) { + saveRule(other->getRule(name)); + } +} + +void DownloadRuleList::saveRulesToStorage() +{ + QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss"); + qBTRSS.setValue("download_rules", toVariantHash()); +} + +void DownloadRuleList::loadRulesFromStorage() +{ + QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss"); + loadRulesFromVariantHash(qBTRSS.value("download_rules").toHash()); +} + +QVariantHash DownloadRuleList::toVariantHash() const +{ + QVariantHash ret; + foreach (const DownloadRulePtr &rule, m_rules.values()) { + ret.insert(rule->name(), rule->toVariantHash()); + } + return ret; +} + +void DownloadRuleList::loadRulesFromVariantHash(const QVariantHash &h) +{ + QVariantHash::ConstIterator it = h.begin(); + QVariantHash::ConstIterator itend = h.end(); + for ( ; it != itend; ++it) { + DownloadRulePtr rule = DownloadRule::fromVariantHash(it.value().toHash()); + if (rule && !rule->name().isEmpty()) + saveRule(rule); + } +} + +void DownloadRuleList::saveRule(const DownloadRulePtr &rule) +{ + qDebug() << Q_FUNC_INFO << rule->name(); + Q_ASSERT(rule); + if (m_rules.contains(rule->name())) { + qDebug("This is an update, removing old rule first"); + removeRule(rule->name()); + } + m_rules.insert(rule->name(), rule); + // Update feedRules hashtable + foreach (const QString &feedUrl, rule->rssFeeds()) { + m_feedRules[feedUrl].append(rule->name()); + } + qDebug() << Q_FUNC_INFO << "EXIT"; +} + +void DownloadRuleList::removeRule(const QString &name) +{ + qDebug() << Q_FUNC_INFO << name; + if (!m_rules.contains(name)) return; + DownloadRulePtr rule = m_rules.take(name); + // Update feedRules hashtable + foreach (const QString &feedUrl, rule->rssFeeds()) { + m_feedRules[feedUrl].removeOne(rule->name()); + } +} + +void DownloadRuleList::renameRule(const QString &oldName, const QString &newName) +{ + if (!m_rules.contains(oldName)) return; + + DownloadRulePtr rule = m_rules.take(oldName); + rule->setName(newName); + m_rules.insert(newName, rule); + // Update feedRules hashtable + foreach (const QString &feedUrl, rule->rssFeeds()) { + m_feedRules[feedUrl].replace(m_feedRules[feedUrl].indexOf(oldName), newName); + } +} + +DownloadRulePtr DownloadRuleList::getRule(const QString &name) const +{ + return m_rules.value(name); +} + +QStringList DownloadRuleList::ruleNames() const +{ + return m_rules.keys(); +} + +bool DownloadRuleList::isEmpty() const +{ + return m_rules.isEmpty(); +} + +bool DownloadRuleList::serialize(const QString &path) +{ + QFile f(path); + if (f.open(QIODevice::WriteOnly)) { + QDataStream out(&f); + out.setVersion(QDataStream::Qt_4_5); + out << toVariantHash(); + f.close(); + return true; + } + + return false; +} + +bool DownloadRuleList::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; + } +} diff --git a/src/gui/rss/rssdownloadrulelist.h b/src/base/rss/rssdownloadrulelist.h similarity index 60% rename from src/gui/rss/rssdownloadrulelist.h rename to src/base/rss/rssdownloadrulelist.h index 4a20c4744..2dc8c36f6 100644 --- a/src/gui/rss/rssdownloadrulelist.h +++ b/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 * * This program is free software; you can redistribute it and/or @@ -34,36 +34,40 @@ #include #include #include + #include "rssdownloadrule.h" -class RssDownloadRuleList +namespace Rss { - Q_DISABLE_COPY(RssDownloadRuleList) + class DownloadRuleList + { + Q_DISABLE_COPY(DownloadRuleList) -public: - 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); + public: + DownloadRuleList(); -private: - void loadRulesFromStorage(); - void loadRulesFromVariantHash(const QVariantHash& l); - QVariantHash toVariantHash() const; + DownloadRulePtr findMatchingRule(const QString &feedUrl, const QString &articleTitle) const; + // Operators + void saveRule(const DownloadRulePtr &rule); + void removeRule(const QString &name); + void renameRule(const QString &oldName, const QString &newName); + DownloadRulePtr 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(DownloadRuleList *other); -private: - QHash m_rules; - QHash m_feedRules; + private: + void loadRulesFromStorage(); + void loadRulesFromVariantHash(const QVariantHash &l); + QVariantHash toVariantHash() const; -}; + private: + QHash m_rules; + QHash m_feedRules; + }; +} #endif // RSSDOWNLOADFILTERLIST_H diff --git a/src/base/rss/rssfeed.cpp b/src/base/rss/rssfeed.cpp new file mode 100644 index 000000000..066964045 --- /dev/null +++ b/src/base/rss/rssfeed.cpp @@ -0,0 +1,449 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2015 Vladimir Golovnev + * Copyright (C) 2010 Christophe Dumez + * Copyright (C) 2010 Arnaud Demaiziere + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + * + * Contact: chris@qbittorrent.org, arnaud@qbittorrent.org + */ + +#include + +#include "base/preferences.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/fs.h" +#include "base/net/downloadmanager.h" +#include "base/net/downloadhandler.h" +#include "private/rssparser.h" +#include "rssdownloadrulelist.h" +#include "rssarticle.h" +#include "rssfolder.h" +#include "rssmanager.h" +#include "rssfeed.h" + +namespace Rss +{ + bool articleDateRecentThan(const ArticlePtr &left, const ArticlePtr &right) + { + return left->date() > right->date(); + } +} + +using namespace Rss; + +Feed::Feed(const QString &url, Manager *manager) + : m_manager(manager) + , m_url (QUrl::fromEncoded(url.toUtf8()).toString()) + , m_icon(":/icons/oxygen/application-rss+xml.png") + , m_unreadCount(0) + , m_dirty(false) + , m_inErrorState(false) + , m_loading(false) +{ + qDebug() << Q_FUNC_INFO << m_url; + m_parser = new Private::Parser; + m_parser->moveToThread(m_manager->workingThread()); + connect(this, SIGNAL(destroyed()), m_parser, SLOT(deleteLater())); + // Listen for new RSS downloads + connect(m_parser, SIGNAL(feedTitle(QString)), SLOT(handleFeedTitle(QString))); + connect(m_parser, SIGNAL(newArticle(QVariantHash)), SLOT(handleNewArticle(QVariantHash))); + connect(m_parser, SIGNAL(finished(QString)), SLOT(handleParsingFinished(QString))); + + // Download the RSS Feed icon + Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(iconUrl(), true); + connect(handler, SIGNAL(downloadFinished(QString, QString)), this, SLOT(handleIconDownloadFinished(QString, QString))); + + // Load old RSS articles + loadItemsFromDisk(); + + refresh(); +} + +Feed::~Feed() +{ + if (!m_icon.startsWith(":/") && QFile::exists(m_icon)) + Utils::Fs::forceRemove(m_icon); +} + +void Feed::saveItemsToDisk() +{ + qDebug() << Q_FUNC_INFO << m_url; + if (!m_dirty) return; + + m_dirty = false; + + QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss"); + QVariantList oldItems; + + ArticleHash::ConstIterator it = m_articles.begin(); + ArticleHash::ConstIterator itend = m_articles.end(); + for ( ; it != itend; ++it) { + oldItems << it.value()->toHash(); + } + qDebug("Saving %d old items for feed %s", oldItems.size(), qPrintable(displayName())); + QHash allOldItems = qBTRSS.value("old_items", QHash()).toHash(); + allOldItems[m_url] = oldItems; + qBTRSS.setValue("old_items", allOldItems); +} + +void Feed::loadItemsFromDisk() +{ + QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss"); + QHash allOldItems = qBTRSS.value("old_items", QHash()).toHash(); + const QVariantList oldItems = allOldItems.value(m_url, QVariantList()).toList(); + qDebug("Loading %d old items for feed %s", oldItems.size(), qPrintable(displayName())); + + foreach (const QVariant &var_it, oldItems) { + QVariantHash item = var_it.toHash(); + ArticlePtr rssItem = Article::fromHash(this, item); + if (rssItem) + addArticle(rssItem); + } +} + +void Feed::addArticle(const ArticlePtr &article) +{ + int maxArticles = Preferences::instance()->getRSSMaxArticlesPerFeed(); + + if (!m_articles.contains(article->guid())) { + m_dirty = true; + + // Update unreadCount + if (!article->isRead()) + ++m_unreadCount; + // Insert in hash table + m_articles[article->guid()] = article; + if (!article->isRead()) // Optimization + connect(article.data(), SIGNAL(articleWasRead()), SLOT(handleArticleRead()), Qt::UniqueConnection); + // Insertion sort + ArticleList::Iterator lowerBound = qLowerBound(m_articlesByDate.begin(), m_articlesByDate.end(), article, articleDateRecentThan); + m_articlesByDate.insert(lowerBound, article); + int lbIndex = m_articlesByDate.indexOf(article); + if (m_articlesByDate.size() > maxArticles) { + ArticlePtr 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(article); + } + } + else { + // m_articles.contains(article->guid()) + // Try to download skipped articles + if (Preferences::instance()->isRssDownloadingEnabled()) { + ArticlePtr skipped = m_articles.value(article->guid(), ArticlePtr()); + if (skipped) { + if (!skipped->isRead()) + downloadArticleTorrentIfMatching(skipped); + } + } + } +} + +bool Feed::refresh() +{ + if (m_loading) { + qWarning() << Q_FUNC_INFO << "Feed" << displayName() << "is already being refreshed, ignoring request"; + return false; + } + m_loading = true; + // Download the RSS again + Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(m_url); + connect(handler, SIGNAL(downloadFinished(QString, QByteArray)), this, SLOT(handleRssDownloadFinished(QString, QByteArray))); + connect(handler, SIGNAL(downloadFailed(QString, QString)), this, SLOT(handleRssDownloadFailed(QString, QString))); + return true; +} + +QString Feed::id() const +{ + return m_url; +} + +void Feed::removeAllSettings() +{ + qDebug() << "Removing all settings / history for feed: " << m_url; + QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss"); + QVariantHash feedsWDownloader = qBTRSS.value("downloader_on", QVariantHash()).toHash(); + if (feedsWDownloader.contains(m_url)) { + feedsWDownloader.remove(m_url); + qBTRSS.setValue("downloader_on", feedsWDownloader); + } + QVariantHash allFeedsFilters = qBTRSS.value("feed_filters", QVariantHash()).toHash(); + if (allFeedsFilters.contains(m_url)) { + allFeedsFilters.remove(m_url); + qBTRSS.setValue("feed_filters", allFeedsFilters); + } + QVariantHash allOldItems = qBTRSS.value("old_items", QVariantHash()).toHash(); + if (allOldItems.contains(m_url)) { + allOldItems.remove(m_url); + qBTRSS.setValue("old_items", allOldItems); + } +} + +bool Feed::isLoading() const +{ + return m_loading; +} + +QString Feed::title() const +{ + return m_title; +} + +void Feed::rename(const QString &newName) +{ + qDebug() << "Renaming stream to" << newName; + m_alias = newName; +} + +// Return the alias if the stream has one, the url if it has no alias +QString Feed::displayName() const +{ + if (!m_alias.isEmpty()) + return m_alias; + if (!m_title.isEmpty()) + return m_title; + return m_url; +} + +QString Feed::url() const +{ + return m_url; +} + +QString Feed::iconPath() const +{ + if (m_inErrorState) + return QLatin1String(":/icons/oxygen/unavailable.png"); + + return m_icon; +} + +bool Feed::hasCustomIcon() const +{ + return !m_icon.startsWith(":/"); +} + +void Feed::setIconPath(const QString &path) +{ + if (!path.isEmpty() && QFile::exists(path)) + m_icon = path; +} + +ArticlePtr Feed::getItem(const QString &guid) const +{ + return m_articles.value(guid); +} + +uint Feed::count() const +{ + return m_articles.size(); +} + +void Feed::markAsRead() +{ + ArticleHash::ConstIterator it = m_articles.begin(); + ArticleHash::ConstIterator itend = m_articles.end(); + for ( ; it != itend; ++it) { + it.value()->markAsRead(); + } + m_unreadCount = 0; + m_manager->forwardFeedInfosChanged(m_url, displayName(), 0); +} + +uint Feed::unreadCount() const +{ + return m_unreadCount; +} + +ArticleList Feed::articleListByDateDesc() const +{ + return m_articlesByDate; +} + +const ArticleHash &Feed::articleHash() const +{ + return m_articles; +} + +ArticleList Feed::unreadArticleListByDateDesc() const +{ + ArticleList unreadNews; + + ArticleList::ConstIterator it = m_articlesByDate.begin(); + ArticleList::ConstIterator itend = m_articlesByDate.end(); + for ( ; it != itend; ++it) { + if (!(*it)->isRead()) + unreadNews << *it; + } + return unreadNews; +} + +// download the icon from the address +QString Feed::iconUrl() const +{ + // XXX: This works for most sites but it is not perfect + return QString("http://%1/favicon.ico").arg(QUrl(m_url).host()); +} + +void Feed::handleIconDownloadFinished(const QString &url, const QString &filePath) +{ + Q_UNUSED(url); + m_icon = filePath; + qDebug() << Q_FUNC_INFO << "icon path:" << m_icon; + m_manager->forwardFeedIconChanged(m_url, m_icon); +} + +void Feed::handleRssDownloadFinished(const QString &url, const QByteArray &data) +{ + Q_UNUSED(url); + qDebug() << Q_FUNC_INFO << "Successfully downloaded RSS feed at" << m_url; + // Parse the download RSS + QMetaObject::invokeMethod(m_parser, "parse", Qt::QueuedConnection, Q_ARG(QByteArray, data)); +} + +void Feed::handleRssDownloadFailed(const QString &url, const QString &error) +{ + Q_UNUSED(url); + m_inErrorState = true; + m_loading = false; + m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount); + qWarning() << "Failed to download RSS feed at" << m_url; + qWarning() << "Reason:" << error; +} + +void Feed::handleFeedTitle(const QString &title) +{ + if (m_title == title) return; + + m_title = title; + + // Notify that we now have something better than a URL to display + if (m_alias.isEmpty()) + m_manager->forwardFeedInfosChanged(m_url, title, m_unreadCount); +} + +void Feed::downloadArticleTorrentIfMatching(const ArticlePtr &article) +{ + Q_ASSERT(Preferences::instance()->isRssDownloadingEnabled()); + DownloadRuleList *rules = m_manager->downloadRules(); + DownloadRulePtr matchingRule = rules->findMatchingRule(m_url, article->title()); + if (!matchingRule) return; + + if (matchingRule->ignoreDays() > 0) { + QDateTime lastMatch = matchingRule->lastMatch(); + if (lastMatch.isValid()) { + if (QDateTime::currentDateTime() < lastMatch.addDays(matchingRule->ignoreDays())) { + article->markAsRead(); + return; + } + } + } + + matchingRule->setLastMatch(QDateTime::currentDateTime()); + rules->saveRulesToStorage(); + // Download the torrent + const QString &torrentUrl = article->torrentUrl(); + if (torrentUrl.isEmpty()) { + Logger::instance()->addMessage(tr("Automatic download of '%1' from '%2' RSS feed failed because it doesn't contain a torrent or a magnet link...").arg(article->title()).arg(displayName()), Log::WARNING); + article->markAsRead(); + return; + } + + Logger::instance()->addMessage(tr("Automatically downloading '%1' torrent from '%2' RSS feed...").arg(article->title()).arg(displayName())); + if (BitTorrent::MagnetUri(torrentUrl).isValid()) + article->markAsRead(); + else + connect(BitTorrent::Session::instance(), SIGNAL(downloadFromUrlFinished(QString)), article.data(), SLOT(handleTorrentDownloadSuccess(const QString&)), Qt::UniqueConnection); + + BitTorrent::AddTorrentParams params; + params.savePath = matchingRule->savePath(); + params.label = matchingRule->label(); + if (matchingRule->addPaused() == DownloadRule::ALWAYS_PAUSED) + params.addPaused = TriStateBool::True; + else if (matchingRule->addPaused() == DownloadRule::NEVER_PAUSED) + params.addPaused = TriStateBool::False; + BitTorrent::Session::instance()->addTorrent(torrentUrl, params); +} + +void Feed::recheckRssItemsForDownload() +{ + Q_ASSERT(Preferences::instance()->isRssDownloadingEnabled()); + foreach (const ArticlePtr &article, m_articlesByDate) { + if (!article->isRead()) + downloadArticleTorrentIfMatching(article); + } +} + +void Feed::handleNewArticle(const QVariantHash &articleData) +{ + ArticlePtr article = Article::fromHash(this, articleData); + if (article.isNull()) { + qDebug() << "Article hash corrupted or guid is uncomputable; feed url: " << m_url; + return; + } + Q_ASSERT(article); + addArticle(article); + + m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount); + // FIXME: We should forward the information here but this would seriously decrease + // performance with current design. + //m_manager->forwardFeedContentChanged(m_url); +} + +void Feed::handleParsingFinished(const QString &error) +{ + if (!error.isEmpty()) { + qWarning() << "Failed to parse RSS feed at" << m_url; + qWarning() << "Reason:" << error; + } + + m_loading = false; + m_inErrorState = !error.isEmpty(); + + m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount); + // XXX: Would not be needed if we did this in handleNewArticle() instead + m_manager->forwardFeedContentChanged(m_url); + + saveItemsToDisk(); +} + +void Feed::handleArticleRead() +{ + --m_unreadCount; + m_dirty = true; + m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount); +} diff --git a/src/base/rss/rssfeed.h b/src/base/rss/rssfeed.h new file mode 100644 index 000000000..b457a04b4 --- /dev/null +++ b/src/base/rss/rssfeed.h @@ -0,0 +1,122 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2015 Vladimir Golovnev + * Copyright (C) 2010 Christophe Dumez + * Copyright (C) 2010 Arnaud Demaiziere + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + * + * Contact: chris@qbittorrent.org, arnaud@qbittorrent.org + */ + +#ifndef RSSFEED_H +#define RSSFEED_H + +#include +#include +#include +#include +#include + +#include "rssfile.h" + +namespace Rss +{ + class Folder; + class Feed; + class Manager; + class DownloadRuleList; + + typedef QHash ArticleHash; + typedef QSharedPointer FeedPtr; + typedef QList FeedList; + + namespace Private + { + class Parser; + } + + bool articleDateRecentThan(const ArticlePtr &left, const ArticlePtr &right); + + class Feed: public QObject, public File + { + Q_OBJECT + + public: + Feed(const QString &url, Manager *manager); + ~Feed(); + + bool refresh(); + QString id() const; + void removeAllSettings(); + void saveItemsToDisk(); + bool isLoading() const; + QString title() const; + void rename(const QString &newName); + QString displayName() const; + QString url() const; + QString iconPath() const; + bool hasCustomIcon() const; + void setIconPath(const QString &pathHierarchy); + ArticlePtr getItem(const QString &guid) const; + uint count() const; + void markAsRead(); + uint unreadCount() const; + ArticleList articleListByDateDesc() const; + const ArticleHash &articleHash() const; + ArticleList unreadArticleListByDateDesc() const; + void recheckRssItemsForDownload(); + + private slots: + void handleIconDownloadFinished(const QString &url, const QString &filePath); + void handleRssDownloadFinished(const QString &url, const QByteArray &data); + void handleRssDownloadFailed(const QString &url, const QString &error); + void handleFeedTitle(const QString &title); + void handleNewArticle(const QVariantHash &article); + void handleParsingFinished(const QString &error); + void handleArticleRead(); + + private: + QString iconUrl() const; + void loadItemsFromDisk(); + void addArticle(const ArticlePtr &article); + void downloadArticleTorrentIfMatching(const ArticlePtr &article); + + private: + Manager *m_manager; + Private::Parser *m_parser; + ArticleHash m_articles; + ArticleList m_articlesByDate; // Articles sorted by date (more recent first) + QString m_title; + QString m_url; + QString m_alias; + QString m_icon; + uint m_unreadCount; + bool m_dirty; + bool m_inErrorState; + bool m_loading; + }; +} + +#endif // RSSFEED_H diff --git a/src/gui/rss/rssfile.cpp b/src/base/rss/rssfile.cpp similarity index 76% rename from src/gui/rss/rssfile.cpp rename to src/base/rss/rssfile.cpp index f41912484..394bd56f0 100644 --- a/src/gui/rss/rssfile.cpp +++ b/src/base/rss/rssfile.cpp @@ -1,6 +1,7 @@ /* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2010 Christophe Dumez + * Copyright (C) 2010 Arnaud Demaiziere * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -28,13 +29,23 @@ * Contact: chris@qbittorrent.org, arnaud@qbittorrent.org */ -#include "rssfile.h" #include "rssfolder.h" +#include "rssfile.h" -QStringList RssFile::pathHierarchy() const { - QStringList path; - if (parent()) - path << parent()->pathHierarchy(); - path << id(); - return path; +using namespace Rss; + +File::~File() {} + +Folder *File::parentFolder() const +{ + return m_parent; +} + +QStringList File::pathHierarchy() const +{ + QStringList path; + if (m_parent) + path << m_parent->pathHierarchy(); + path << id(); + return path; } diff --git a/src/gui/rss/rssfile.h b/src/base/rss/rssfile.h similarity index 53% rename from src/gui/rss/rssfile.h rename to src/base/rss/rssfile.h index 3d8d95f4e..285da93f6 100644 --- a/src/gui/rss/rssfile.h +++ b/src/base/rss/rssfile.h @@ -1,6 +1,7 @@ /* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2010 Christophe Dumez + * Copyright (C) 2010 Arnaud Demaiziere * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -31,45 +32,51 @@ #ifndef RSSFILE_H #define RSSFILE_H -#include #include #include #include -class RssFolder; -class RssFile; -class RssArticle; +namespace Rss +{ + class Folder; + class File; + class Article; -typedef QSharedPointer RssFilePtr; -typedef QSharedPointer RssArticlePtr; -typedef QList RssArticleList; -typedef QList RssFileList; + typedef QSharedPointer FilePtr; + typedef QSharedPointer
ArticlePtr; + typedef QList ArticleList; + typedef QList FileList; -/** - * Parent interface for RssFolder and RssFeed. - */ -class RssFile { -public: - virtual ~RssFile() {} + /** + * Parent interface for Rss::Folder and Rss::Feed. + */ + class File + { + public: + virtual ~File(); - virtual uint unreadCount() const = 0; - virtual QString displayName() const = 0; - virtual QString id() const = 0; - virtual QIcon icon() const = 0; - virtual void rename(const QString &new_name) = 0; - virtual void markAsRead() = 0; - virtual RssFolder* parent() const = 0; - virtual void setParent(RssFolder* parent) = 0; - virtual bool refresh() = 0; - virtual RssArticleList articleListByDateDesc() const = 0; - virtual RssArticleList unreadArticleListByDateDesc() const = 0; - virtual void removeAllSettings() = 0; - virtual void saveItemsToDisk() = 0; - virtual void recheckRssItemsForDownload() = 0; - QStringList pathHierarchy() const; + virtual QString id() const = 0; + virtual QString displayName() const = 0; + virtual uint unreadCount() const = 0; + virtual QString iconPath() const = 0; + virtual ArticleList articleListByDateDesc() const = 0; + virtual ArticleList unreadArticleListByDateDesc() const = 0; -protected: - uint m_unreadCount; -}; + virtual void rename(const QString &newName) = 0; + virtual void markAsRead() = 0; + virtual bool refresh() = 0; + virtual void removeAllSettings() = 0; + virtual void saveItemsToDisk() = 0; + virtual void recheckRssItemsForDownload() = 0; + + Folder *parentFolder() const; + QStringList pathHierarchy() const; + + protected: + friend class Folder; + + Folder *m_parent = nullptr; + }; +} #endif // RSSFILE_H diff --git a/src/base/rss/rssfolder.cpp b/src/base/rss/rssfolder.cpp new file mode 100644 index 000000000..e17afecb9 --- /dev/null +++ b/src/base/rss/rssfolder.cpp @@ -0,0 +1,253 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2010 Christophe Dumez + * Copyright (C) 2010 Arnaud Demaiziere + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + * + * Contact: chris@qbittorrent.org, arnaud@qbittorrent.org + */ + +#include + +#include "base/iconprovider.h" +#include "base/bittorrent/session.h" +#include "rssmanager.h" +#include "rssfeed.h" +#include "rssarticle.h" +#include "rssfolder.h" + +using namespace Rss; + +Folder::Folder(const QString &name) + : m_name(name) +{ +} + +uint Folder::unreadCount() const +{ + uint nbUnread = 0; + + FileHash::ConstIterator it = m_children.begin(); + FileHash::ConstIterator itend = m_children.end(); + for ( ; it != itend; ++it) + nbUnread += it.value()->unreadCount(); + + return nbUnread; +} + +void Folder::removeChild(const QString &childId) +{ + if (m_children.contains(childId)) { + FilePtr child = m_children.take(childId); + child->removeAllSettings(); + } +} + +// Refresh All Children +bool Folder::refresh() +{ + FileHash::ConstIterator it = m_children.begin(); + FileHash::ConstIterator itend = m_children.end(); + bool refreshed = false; + for ( ; it != itend; ++it) { + if (it.value()->refresh()) + refreshed = true; + } + return refreshed; +} + +ArticleList Folder::articleListByDateDesc() const +{ + ArticleList news; + + FileHash::ConstIterator it = m_children.begin(); + FileHash::ConstIterator itend = m_children.end(); + for ( ; it != itend; ++it) { + int n = news.size(); + news << it.value()->articleListByDateDesc(); + std::inplace_merge(news.begin(), news.begin() + n, news.end(), articleDateRecentThan); + } + return news; +} + +ArticleList Folder::unreadArticleListByDateDesc() const +{ + ArticleList unreadNews; + + FileHash::ConstIterator it = m_children.begin(); + FileHash::ConstIterator itend = m_children.end(); + for ( ; it != itend; ++it) { + int n = unreadNews.size(); + unreadNews << it.value()->unreadArticleListByDateDesc(); + std::inplace_merge(unreadNews.begin(), unreadNews.begin() + n, unreadNews.end(), articleDateRecentThan); + } + return unreadNews; +} + +FileList Folder::getContent() const +{ + return m_children.values(); +} + +uint Folder::getNbFeeds() const +{ + uint nbFeeds = 0; + + FileHash::ConstIterator it = m_children.begin(); + FileHash::ConstIterator itend = m_children.end(); + for ( ; it != itend; ++it) { + if (FolderPtr folder = qSharedPointerDynamicCast(it.value())) + nbFeeds += folder->getNbFeeds(); + else + ++nbFeeds; // Feed + } + return nbFeeds; +} + +QString Folder::displayName() const +{ + return m_name; +} + +void Folder::rename(const QString &newName) +{ + if (m_name == newName) return; + + Q_ASSERT(!m_parent->hasChild(newName)); + if (!m_parent->hasChild(newName)) { + // Update parent + FilePtr folder = m_parent->m_children.take(m_name); + m_parent->m_children[newName] = folder; + // Actually rename + m_name = newName; + } +} + +void Folder::markAsRead() +{ + FileHash::ConstIterator it = m_children.begin(); + FileHash::ConstIterator itend = m_children.end(); + for ( ; it != itend; ++it) { + it.value()->markAsRead(); + } +} + +FeedList Folder::getAllFeeds() const +{ + FeedList streams; + + FileHash::ConstIterator it = m_children.begin(); + FileHash::ConstIterator itend = m_children.end(); + for ( ; it != itend; ++it) { + if (FeedPtr feed = qSharedPointerDynamicCast(it.value())) + streams << feed; + else if (FolderPtr folder = qSharedPointerDynamicCast(it.value())) + streams << folder->getAllFeeds(); + } + return streams; +} + +QHash Folder::getAllFeedsAsHash() const +{ + QHash ret; + + FileHash::ConstIterator it = m_children.begin(); + FileHash::ConstIterator itend = m_children.end(); + for ( ; it != itend; ++it) { + if (FeedPtr feed = qSharedPointerDynamicCast(it.value())) { + qDebug() << Q_FUNC_INFO << feed->url(); + ret[feed->url()] = feed; + } + else if (FolderPtr folder = qSharedPointerDynamicCast(it.value())) { + ret.unite(folder->getAllFeedsAsHash()); + } + } + return ret; +} + +bool Folder::addFile(const FilePtr &item) +{ + Q_ASSERT(!m_children.contains(item->id())); + if (!m_children.contains(item->id())) { + m_children[item->id()] = item; + // Update parent + item->m_parent = this; + return true; + } + + return false; +} + +void Folder::removeAllItems() +{ + m_children.clear(); +} + +FilePtr Folder::child(const QString &childId) +{ + return m_children.value(childId); +} + +void Folder::removeAllSettings() +{ + FileHash::ConstIterator it = m_children.begin(); + FileHash::ConstIterator itend = m_children.end(); + for ( ; it != itend; ++it) + it.value()->removeAllSettings(); +} + +void Folder::saveItemsToDisk() +{ + foreach (const FilePtr &child, m_children.values()) + child->saveItemsToDisk(); +} + +QString Folder::id() const +{ + return m_name; +} + +QString Folder::iconPath() const +{ + return IconProvider::instance()->getIconPath("inode-directory"); +} + +bool Folder::hasChild(const QString &childId) +{ + return m_children.contains(childId); +} + +FilePtr Folder::takeChild(const QString &childId) +{ + return m_children.take(childId); +} + +void Folder::recheckRssItemsForDownload() +{ + FileHash::ConstIterator it = m_children.begin(); + FileHash::ConstIterator itend = m_children.end(); + for ( ; it != itend; ++it) + it.value()->recheckRssItemsForDownload(); +} diff --git a/src/base/rss/rssfolder.h b/src/base/rss/rssfolder.h new file mode 100644 index 000000000..7adefb423 --- /dev/null +++ b/src/base/rss/rssfolder.h @@ -0,0 +1,86 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2010 Christophe Dumez + * Copyright (C) 2010 Arnaud Demaiziere + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + * + * Contact: chris@qbittorrent.org, arnaud@qbittorrent.org + */ + +#ifndef RSSFOLDER_H +#define RSSFOLDER_H + +#include +#include + +#include "rssfile.h" + +namespace Rss +{ + class Folder; + class Feed; + class Manager; + + typedef QHash FileHash; + typedef QSharedPointer FeedPtr; + typedef QSharedPointer FolderPtr; + typedef QList FeedList; + + class Folder: public File + { + public: + explicit Folder(const QString &name = QString()); + + uint unreadCount() const; + uint getNbFeeds() const; + FileList getContent() const; + FeedList getAllFeeds() const; + QHash getAllFeedsAsHash() const; + QString displayName() const; + QString id() const; + QString iconPath() const; + bool hasChild(const QString &childId); + ArticleList articleListByDateDesc() const; + ArticleList unreadArticleListByDateDesc() const; + + void rename(const QString &newName); + void markAsRead(); + bool refresh(); + void removeAllSettings(); + void saveItemsToDisk(); + void recheckRssItemsForDownload(); + void removeAllItems(); + FilePtr child(const QString &childId); + FilePtr takeChild(const QString &childId); + bool addFile(const FilePtr &item); + void removeChild(const QString &childId); + + private: + QString m_name; + FileHash m_children; + }; +} + +#endif // RSSFOLDER_H diff --git a/src/base/rss/rssmanager.cpp b/src/base/rss/rssmanager.cpp new file mode 100644 index 000000000..8fc936d6e --- /dev/null +++ b/src/base/rss/rssmanager.cpp @@ -0,0 +1,190 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2010 Christophe Dumez + * Copyright (C) 2010 Arnaud Demaiziere + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + * + * Contact: chris@qbittorrent.org, arnaud@qbittorrent.org + */ + +#include + +#include "base/logger.h" +#include "base/preferences.h" +#include "rssfolder.h" +#include "rssfeed.h" +#include "rssarticle.h" +#include "rssdownloadrulelist.h" +#include "rssmanager.h" + +static const int MSECS_PER_MIN = 60000; + +using namespace Rss; +using namespace Rss::Private; + +Manager::Manager(QObject *parent) + : QObject(parent) + , m_downloadRules(new DownloadRuleList) + , m_rootFolder(new Folder) + , m_workingThread(new QThread(this)) +{ + m_workingThread->start(); + connect(&m_refreshTimer, SIGNAL(timeout()), SLOT(refresh())); + m_refreshInterval = Preferences::instance()->getRSSRefreshInterval(); + m_refreshTimer.start(m_refreshInterval * MSECS_PER_MIN); +} + +Manager::~Manager() +{ + qDebug("Deleting RSSManager..."); + m_workingThread->quit(); + m_workingThread->wait(); + delete m_downloadRules; + m_rootFolder->saveItemsToDisk(); + saveStreamList(); + m_rootFolder.clear(); + qDebug("RSSManager deleted"); +} + +void Manager::updateRefreshInterval(uint val) +{ + if (m_refreshInterval != val) { + m_refreshInterval = val; + m_refreshTimer.start(m_refreshInterval*60000); + qDebug("New RSS refresh interval is now every %dmin", m_refreshInterval); + } +} + +void Manager::loadStreamList() +{ + const Preferences *const pref = Preferences::instance(); + const QStringList streamsUrl = pref->getRssFeedsUrls(); + const QStringList aliases = pref->getRssFeedsAliases(); + if (streamsUrl.size() != aliases.size()) { + Logger::instance()->addMessage("Corrupted RSS list, not loading it.", Log::WARNING); + 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 feedUrl = path.takeLast(); + qDebug() << "Feed URL:" << feedUrl; + // Create feed path (if it does not exists) + FolderPtr feedParent = m_rootFolder; + foreach (const QString &folderName, path) { + if (!feedParent->hasChild(folderName)) { + qDebug() << "Adding parent folder:" << folderName; + FolderPtr folder(new Folder(folderName)); + feedParent->addFile(folder); + feedParent = folder; + } + else { + feedParent = qSharedPointerDynamicCast(feedParent->child(folderName)); + } + } + // Create feed + qDebug() << "Adding feed to parent folder"; + FeedPtr stream(new Feed(feedUrl, this)); + feedParent->addFile(stream); + const QString &alias = aliases[i]; + if (!alias.isEmpty()) + stream->rename(alias); + ++i; + } + qDebug("NB RSS streams loaded: %d", streamsUrl.size()); +} + +void Manager::forwardFeedContentChanged(const QString &url) +{ + emit feedContentChanged(url); +} + +void Manager::forwardFeedInfosChanged(const QString &url, const QString &displayName, uint unreadCount) +{ + emit feedInfosChanged(url, displayName, unreadCount); +} + +void Manager::forwardFeedIconChanged(const QString &url, const QString &iconPath) +{ + emit feedIconChanged(url, iconPath); +} + +void Manager::moveFile(const FilePtr &file, const FolderPtr &destinationFolder) +{ + Folder *srcFolder = file->parentFolder(); + if (destinationFolder != srcFolder) { + // Remove reference in old folder + srcFolder->takeChild(file->id()); + // add to new Folder + destinationFolder->addFile(file); + } + else { + qDebug("Nothing to move, same destination folder"); + } +} + +void Manager::saveStreamList() const +{ + QStringList streamsUrl; + QStringList aliases; + FeedList streams = m_rootFolder->getAllFeeds(); + foreach (const FeedPtr &stream, streams) { + // This backslash has nothing to do with path handling + QString streamPath = stream->pathHierarchy().join("\\"); + if (streamPath.isNull()) + streamPath = ""; + qDebug("Saving stream path: %s", qPrintable(streamPath)); + streamsUrl << streamPath; + aliases << stream->displayName(); + } + Preferences *const pref = Preferences::instance(); + pref->setRssFeedsUrls(streamsUrl); + pref->setRssFeedsAliases(aliases); +} + +DownloadRuleList *Manager::downloadRules() const +{ + Q_ASSERT(m_downloadRules); + return m_downloadRules; +} + +FolderPtr Manager::rootFolder() const +{ + return m_rootFolder; +} + +QThread *Manager::workingThread() const +{ + return m_workingThread; +} + +void Manager::refresh() +{ + m_rootFolder->refresh(); +} diff --git a/src/base/rss/rssmanager.h b/src/base/rss/rssmanager.h new file mode 100644 index 000000000..3c7b81738 --- /dev/null +++ b/src/base/rss/rssmanager.h @@ -0,0 +1,90 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2010 Christophe Dumez + * Copyright (C) 2010 Arnaud Demaiziere + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + * + * Contact: chris@qbittorrent.org, arnaud@qbittorrent.org + */ + +#ifndef RSSMANAGER_H +#define RSSMANAGER_H + +#include +#include +#include +#include + +namespace Rss +{ + class DownloadRuleList; + class File; + class Folder; + class Feed; + class Manager; + + typedef QSharedPointer FilePtr; + typedef QSharedPointer FolderPtr; + typedef QSharedPointer FeedPtr; + + typedef QSharedPointer ManagerPtr; + + class Manager: public QObject + { + Q_OBJECT + + public: + explicit Manager(QObject *parent = 0); + ~Manager(); + + DownloadRuleList *downloadRules() const; + FolderPtr rootFolder() const; + QThread *workingThread() const; + + public slots: + void refresh(); + void loadStreamList(); + void saveStreamList() const; + void forwardFeedContentChanged(const QString &url); + void forwardFeedInfosChanged(const QString &url, const QString &displayName, uint unreadCount); + void forwardFeedIconChanged(const QString &url, const QString &iconPath); + void moveFile(const FilePtr &file, const FolderPtr &destinationFolder); + void updateRefreshInterval(uint val); + + signals: + void feedContentChanged(const QString &url); + void feedInfosChanged(const QString &url, const QString &displayName, uint unreadCount); + void feedIconChanged(const QString &url, const QString &iconPath); + + private: + QTimer m_refreshTimer; + uint m_refreshInterval; + DownloadRuleList *m_downloadRules; + FolderPtr m_rootFolder; + QThread *m_workingThread; + }; +} + +#endif // RSSMANAGER_H diff --git a/src/gui/rss/automatedrssdownloader.cpp b/src/gui/rss/automatedrssdownloader.cpp index bf5684a57..5b5493763 100644 --- a/src/gui/rss/automatedrssdownloader.cpp +++ b/src/gui/rss/automatedrssdownloader.cpp @@ -36,16 +36,17 @@ #include "automatedrssdownloader.h" #include "ui_automatedrssdownloader.h" -#include "rssdownloadrulelist.h" +#include "base/rss/rssdownloadrulelist.h" #include "base/preferences.h" -#include "rssmanager.h" -#include "rssfeed.h" +#include "base/rss/rssmanager.h" +#include "base/rss/rssfolder.h" +#include "base/rss/rssfeed.h" #include "guiiconprovider.h" #include "autoexpandabledialog.h" #include "base/utils/fs.h" #include "base/utils/string.h" -AutomatedRssDownloader::AutomatedRssDownloader(const QWeakPointer& manager, QWidget *parent) : +AutomatedRssDownloader::AutomatedRssDownloader(const QWeakPointer& manager, QWidget *parent) : QDialog(parent), ui(new Ui::AutomatedRssDownloader), m_manager(manager), m_editedRule(0) @@ -68,7 +69,7 @@ AutomatedRssDownloader::AutomatedRssDownloader(const QWeakPointer& m ok = connect(ui->listRules, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(displayRulesListMenu(const QPoint&))); Q_ASSERT(ok); m_ruleList = manager.toStrongRef()->downloadRules(); - m_editableRuleList = new RssDownloadRuleList; // Read rule list from disk + m_editableRuleList = new Rss::DownloadRuleList; // Read rule list from disk m_episodeValidator = new QRegExpValidator( QRegExp("^(^[1-9]{1,1}\\d{0,3}x([1-9]{1,1}\\d{0,3}(-([1-9]{1,1}\\d{0,3})?)?;){1,}){1,1}", Qt::CaseInsensitive), @@ -199,7 +200,7 @@ void AutomatedRssDownloader::updateFeedList() const QString feed_url = item->data(Qt::UserRole).toString(); bool all_enabled = false; foreach (const QListWidgetItem *ruleItem, ui->listRules->selectedItems()) { - RssDownloadRulePtr rule = m_editableRuleList->getRule(ruleItem->text()); + Rss::DownloadRulePtr rule = m_editableRuleList->getRule(ruleItem->text()); if (!rule) continue; qDebug() << "Rule" << rule->name() << "affects" << rule->rssFeeds().size() << "feeds."; foreach (QString test, rule->rssFeeds()) { @@ -238,7 +239,7 @@ void AutomatedRssDownloader::updateRuleDefinitionBox() const QList selection = ui->listRules->selectedItems(); if (selection.count() == 1) { m_editedRule = selection.first(); - RssDownloadRulePtr rule = getCurrentRule(); + Rss::DownloadRulePtr rule = getCurrentRule(); if (rule) { ui->lineContains->setText(rule->mustContain()); ui->lineNotContains->setText(rule->mustNotContain()); @@ -300,12 +301,12 @@ void AutomatedRssDownloader::clearRuleDefinitionBox() updateMustNotLineValidity(); } -RssDownloadRulePtr AutomatedRssDownloader::getCurrentRule() const +Rss::DownloadRulePtr AutomatedRssDownloader::getCurrentRule() const { QListWidgetItem * current_item = ui->listRules->currentItem(); if (current_item) return m_editableRuleList->getRule(current_item->text()); - return RssDownloadRulePtr(); + return Rss::DownloadRulePtr(); } void AutomatedRssDownloader::initLabelCombobox() @@ -326,9 +327,9 @@ void AutomatedRssDownloader::saveEditedRule() qDebug() << "Probably removed the item, no need to save it"; return; } - RssDownloadRulePtr rule = m_editableRuleList->getRule(m_editedRule->text()); + Rss::DownloadRulePtr rule = m_editableRuleList->getRule(m_editedRule->text()); if (!rule) { - rule = RssDownloadRulePtr(new RssDownloadRule); + rule = Rss::DownloadRulePtr(new Rss::DownloadRule); rule->setName(m_editedRule->text()); } if (m_editedRule->checkState() == Qt::Unchecked) @@ -344,7 +345,7 @@ void AutomatedRssDownloader::saveEditedRule() else rule->setSavePath(""); rule->setLabel(ui->comboLabel->currentText()); - rule->setAddPaused(RssDownloadRule::AddPausedState(ui->comboAddPaused->currentIndex())); + rule->setAddPaused(Rss::DownloadRule::AddPausedState(ui->comboAddPaused->currentIndex())); // Save new label if (!rule->label().isEmpty()) Preferences::instance()->addTorrentLabelExternal(rule->label()); @@ -498,7 +499,7 @@ void AutomatedRssDownloader::handleFeedCheckStateChange(QListWidgetItem *feed_it } const QString feed_url = feed_item->data(Qt::UserRole).toString(); foreach (QListWidgetItem* rule_item, ui->listRules->selectedItems()) { - RssDownloadRulePtr rule = m_editableRuleList->getRule(rule_item->text()); + Rss::DownloadRulePtr rule = m_editableRuleList->getRule(rule_item->text()); Q_ASSERT(rule); QStringList affected_feeds = rule->rssFeeds(); if (feed_item->checkState() == Qt::Checked) { @@ -521,19 +522,19 @@ void AutomatedRssDownloader::handleFeedCheckStateChange(QListWidgetItem *feed_it void AutomatedRssDownloader::updateMatchingArticles() { ui->treeMatchingArticles->clear(); - RssManagerPtr manager = m_manager.toStrongRef(); + Rss::ManagerPtr manager = m_manager.toStrongRef(); if (!manager) return; - const QHash all_feeds = manager->getAllFeedsAsHash(); + const QHash all_feeds = manager->rootFolder()->getAllFeedsAsHash(); saveEditedRule(); foreach (const QListWidgetItem *rule_item, ui->listRules->selectedItems()) { - RssDownloadRulePtr rule = m_editableRuleList->getRule(rule_item->text()); + Rss::DownloadRulePtr rule = m_editableRuleList->getRule(rule_item->text()); if (!rule) continue; foreach (const QString &feed_url, rule->rssFeeds()) { qDebug() << Q_FUNC_INFO << feed_url; if (!all_feeds.contains(feed_url)) continue; // Feed was removed - RssFeedPtr feed = all_feeds.value(feed_url); + Rss::FeedPtr feed = all_feeds.value(feed_url); Q_ASSERT(feed); if (!feed) continue; const QStringList matching_articles = rule->findMatchingArticles(feed); @@ -543,7 +544,7 @@ void AutomatedRssDownloader::updateMatchingArticles() } } -void AutomatedRssDownloader::addFeedArticlesToTree(const RssFeedPtr& feed, const QStringList &articles) +void AutomatedRssDownloader::addFeedArticlesToTree(const Rss::FeedPtr& feed, const QStringList &articles) { // Check if this feed is already in the tree QTreeWidgetItem *treeFeedItem = 0; diff --git a/src/gui/rss/automatedrssdownloader.h b/src/gui/rss/automatedrssdownloader.h index b3e409a88..8bbda030c 100644 --- a/src/gui/rss/automatedrssdownloader.h +++ b/src/gui/rss/automatedrssdownloader.h @@ -35,7 +35,8 @@ #include #include #include -#include "rssdownloadrule.h" + +#include "base/rss/rssdownloadrule.h" QT_BEGIN_NAMESPACE namespace Ui { @@ -43,8 +44,11 @@ class AutomatedRssDownloader; } QT_END_NAMESPACE -class RssDownloadRuleList; -class RssManager; +namespace Rss +{ + class DownloadRuleList; + class Manager; +} QT_BEGIN_NAMESPACE class QListWidgetItem; @@ -55,7 +59,7 @@ class AutomatedRssDownloader : public QDialog Q_OBJECT public: - explicit AutomatedRssDownloader(const QWeakPointer& manager, QWidget *parent = 0); + explicit AutomatedRssDownloader(const QWeakPointer& manager, QWidget *parent = 0); ~AutomatedRssDownloader(); bool isRssDownloaderEnabled() const; @@ -85,16 +89,16 @@ private slots: void onFinished(int result); private: - RssDownloadRulePtr getCurrentRule() const; + Rss::DownloadRulePtr getCurrentRule() const; void initLabelCombobox(); - void addFeedArticlesToTree(const RssFeedPtr& feed, const QStringList& articles); + void addFeedArticlesToTree(const Rss::FeedPtr& feed, const QStringList& articles); private: Ui::AutomatedRssDownloader *ui; - QWeakPointer m_manager; + QWeakPointer m_manager; QListWidgetItem* m_editedRule; - RssDownloadRuleList *m_ruleList; - RssDownloadRuleList *m_editableRuleList; + Rss::DownloadRuleList *m_ruleList; + Rss::DownloadRuleList *m_editableRuleList; QRegExpValidator *m_episodeValidator; QShortcut *editHotkey; QShortcut *deleteHotkey; diff --git a/src/gui/rss/feedlistwidget.cpp b/src/gui/rss/feedlistwidget.cpp index f86ac9864..6f00ffcbd 100644 --- a/src/gui/rss/feedlistwidget.cpp +++ b/src/gui/rss/feedlistwidget.cpp @@ -28,21 +28,22 @@ * Contact: chris@qbittorrent.org, arnaud@qbittorrent.org */ -#include "feedlistwidget.h" -#include "rssmanager.h" -#include "rssfeed.h" +#include "base/rss/rssmanager.h" +#include "base/rss/rssfolder.h" +#include "base/rss/rssfeed.h" #include "guiiconprovider.h" +#include "feedlistwidget.h" -FeedListWidget::FeedListWidget(QWidget *parent, const RssManagerPtr& rssmanager): QTreeWidget(parent), m_rssManager(rssmanager) { +FeedListWidget::FeedListWidget(QWidget *parent, const Rss::ManagerPtr& rssmanager): QTreeWidget(parent), m_rssManager(rssmanager) { setContextMenuPolicy(Qt::CustomContextMenu); setDragDropMode(QAbstractItemView::InternalMove); setSelectionMode(QAbstractItemView::ExtendedSelection); setColumnCount(1); headerItem()->setText(0, tr("RSS feeds")); m_unreadStickyItem = new QTreeWidgetItem(this); - m_unreadStickyItem->setText(0, tr("Unread") + QString::fromUtf8(" (") + QString::number(rssmanager->unreadCount())+ QString(")")); + m_unreadStickyItem->setText(0, tr("Unread") + QString::fromUtf8(" (") + QString::number(rssmanager->rootFolder()->unreadCount()) + QString(")")); m_unreadStickyItem->setData(0,Qt::DecorationRole, GuiIconProvider::instance()->getIcon("mail-folder-inbox")); - itemAdded(m_unreadStickyItem, rssmanager); + itemAdded(m_unreadStickyItem, rssmanager->rootFolder()); connect(this, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), SLOT(updateCurrentFeed(QTreeWidgetItem*))); setCurrentItem(m_unreadStickyItem); } @@ -51,20 +52,20 @@ FeedListWidget::~FeedListWidget() { delete m_unreadStickyItem; } -void FeedListWidget::itemAdded(QTreeWidgetItem *item, const RssFilePtr& file) { +void FeedListWidget::itemAdded(QTreeWidgetItem *item, const Rss::FilePtr& file) { m_rssMapping[item] = file; - if (RssFeedPtr feed = qSharedPointerDynamicCast(file)) { + if (Rss::FeedPtr feed = qSharedPointerDynamicCast(file)) { m_feedsItems[feed->id()] = item; } } void FeedListWidget::itemAboutToBeRemoved(QTreeWidgetItem *item) { - RssFilePtr file = m_rssMapping.take(item); - if (RssFeedPtr feed = qSharedPointerDynamicCast(file)) { + Rss::FilePtr file = m_rssMapping.take(item); + if (Rss::FeedPtr feed = qSharedPointerDynamicCast(file)) { m_feedsItems.remove(feed->id()); - } if (RssFolderPtr folder = qSharedPointerDynamicCast(file)) { - RssFeedList feeds = folder->getAllFeeds(); - foreach (const RssFeedPtr& feed, feeds) { + } if (Rss::FolderPtr folder = qSharedPointerDynamicCast(file)) { + Rss::FeedList feeds = folder->getAllFeeds(); + foreach (const Rss::FeedPtr& feed, feeds) { m_feedsItems.remove(feed->id()); } } @@ -131,18 +132,18 @@ QList FeedListWidget::getAllFeedItems(QTreeWidgetItem* folder) return feeds; } -RssFilePtr FeedListWidget::getRSSItem(QTreeWidgetItem *item) const { - return m_rssMapping.value(item, RssFilePtr()); +Rss::FilePtr FeedListWidget::getRSSItem(QTreeWidgetItem *item) const { + return m_rssMapping.value(item, Rss::FilePtr()); } bool FeedListWidget::isFeed(QTreeWidgetItem *item) const { - return (qSharedPointerDynamicCast(m_rssMapping.value(item)) != NULL); + return (qSharedPointerDynamicCast(m_rssMapping.value(item)) != NULL); } bool FeedListWidget::isFolder(QTreeWidgetItem *item) const { - return (qSharedPointerDynamicCast(m_rssMapping.value(item)) != NULL); + return (qSharedPointerDynamicCast(m_rssMapping.value(item)) != NULL); } QString FeedListWidget::getItemID(QTreeWidgetItem *item) const { @@ -153,8 +154,8 @@ QTreeWidgetItem* FeedListWidget::getTreeItemFromUrl(const QString &url) const { return m_feedsItems.value(url, 0); } -RssFeedPtr FeedListWidget::getRSSItemFromUrl(const QString &url) const { - return qSharedPointerDynamicCast(getRSSItem(getTreeItemFromUrl(url))); +Rss::FeedPtr FeedListWidget::getRSSItemFromUrl(const QString &url) const { + return qSharedPointerDynamicCast(getRSSItem(getTreeItemFromUrl(url))); } QTreeWidgetItem* FeedListWidget::currentItem() const { @@ -197,17 +198,17 @@ void FeedListWidget::dropEvent(QDropEvent *event) { qDebug("dropEvent"); QList folders_altered; QTreeWidgetItem *dest_folder_item = itemAt(event->pos()); - RssFolderPtr dest_folder; + Rss::FolderPtr dest_folder; if (dest_folder_item) { - dest_folder = qSharedPointerCast(getRSSItem(dest_folder_item)); + dest_folder = qSharedPointerCast(getRSSItem(dest_folder_item)); folders_altered << dest_folder_item; } else { - dest_folder = m_rssManager; + dest_folder = m_rssManager->rootFolder(); } QList src_items = selectedItems(); // Check if there is not going to overwrite another file foreach (QTreeWidgetItem *src_item, src_items) { - RssFilePtr file = getRSSItem(src_item); + Rss::FilePtr file = getRSSItem(src_item); if (dest_folder->hasChild(file->id())) { QTreeWidget::dropEvent(event); return; @@ -219,7 +220,7 @@ void FeedListWidget::dropEvent(QDropEvent *event) { if (parent_folder && !folders_altered.contains(parent_folder)) folders_altered << parent_folder; // Actually move the file - RssFilePtr file = getRSSItem(src_item); + Rss::FilePtr file = getRSSItem(src_item); m_rssManager->moveFile(file, dest_folder); } QTreeWidget::dropEvent(event); diff --git a/src/gui/rss/feedlistwidget.h b/src/gui/rss/feedlistwidget.h index 89f99eb98..edc108cc3 100644 --- a/src/gui/rss/feedlistwidget.h +++ b/src/gui/rss/feedlistwidget.h @@ -39,15 +39,15 @@ #include #include -#include "rssfile.h" -#include "rssfeed.h" -#include "rssmanager.h" +#include "base/rss/rssfile.h" +#include "base/rss/rssfeed.h" +#include "base/rss/rssmanager.h" class FeedListWidget: public QTreeWidget { Q_OBJECT public: - FeedListWidget(QWidget *parent, const RssManagerPtr& rssManager); + FeedListWidget(QWidget *parent, const Rss::ManagerPtr& rssManager); ~FeedListWidget(); bool hasFeed(const QString &url) const; @@ -56,17 +56,17 @@ public: QStringList getItemPath(QTreeWidgetItem* item) const; QList getAllOpenFolders(QTreeWidgetItem *parent=0) const; QList getAllFeedItems(QTreeWidgetItem* folder); - RssFilePtr getRSSItem(QTreeWidgetItem *item) const; + Rss::FilePtr getRSSItem(QTreeWidgetItem *item) const; bool isFeed(QTreeWidgetItem *item) const; bool isFolder(QTreeWidgetItem *item) const; QString getItemID(QTreeWidgetItem *item) const; QTreeWidgetItem* getTreeItemFromUrl(const QString &url) const; - RssFeedPtr getRSSItemFromUrl(const QString &url) const; + Rss::FeedPtr getRSSItemFromUrl(const QString &url) const; QTreeWidgetItem* currentItem() const; QTreeWidgetItem* currentFeed() const; public slots: - void itemAdded(QTreeWidgetItem *item, const RssFilePtr& file); + void itemAdded(QTreeWidgetItem *item, const Rss::FilePtr& file); void itemAboutToBeRemoved(QTreeWidgetItem *item); signals: @@ -80,8 +80,8 @@ protected: void dropEvent(QDropEvent *event); private: - RssManagerPtr m_rssManager; - QHash m_rssMapping; + Rss::ManagerPtr m_rssManager; + QHash m_rssMapping; QHash m_feedsItems; QTreeWidgetItem* m_currentFeed; QTreeWidgetItem *m_unreadStickyItem; diff --git a/src/gui/rss/rss.pri b/src/gui/rss/rss.pri index e4d91389b..01df487de 100644 --- a/src/gui/rss/rss.pri +++ b/src/gui/rss/rss.pri @@ -3,31 +3,15 @@ INCLUDEPATH += $$PWD HEADERS += $$PWD/rss_imp.h \ $$PWD/rsssettingsdlg.h \ $$PWD/feedlistwidget.h \ - $$PWD/rssmanager.h \ - $$PWD/rssfeed.h \ - $$PWD/rssfolder.h \ - $$PWD/rssfile.h \ - $$PWD/rssarticle.h \ $$PWD/automatedrssdownloader.h \ - $$PWD/rssdownloadrule.h \ - $$PWD/rssdownloadrulelist.h \ $$PWD/cookiesdlg.h \ - $$PWD/rssparser.h \ $$PWD/htmlbrowser.h SOURCES += $$PWD/rss_imp.cpp \ $$PWD/rsssettingsdlg.cpp \ $$PWD/feedlistwidget.cpp \ - $$PWD/rssmanager.cpp \ - $$PWD/rssfeed.cpp \ - $$PWD/rssfolder.cpp \ - $$PWD/rssarticle.cpp \ $$PWD/automatedrssdownloader.cpp \ - $$PWD/rssdownloadrule.cpp \ - $$PWD/rssdownloadrulelist.cpp \ $$PWD/cookiesdlg.cpp \ - $$PWD/rssfile.cpp \ - $$PWD/rssparser.cpp \ $$PWD/htmlbrowser.cpp FORMS += $$PWD/rss.ui \ diff --git a/src/gui/rss/rss_imp.cpp b/src/gui/rss/rss_imp.cpp index 106b7cb30..002ca17e5 100644 --- a/src/gui/rss/rss_imp.cpp +++ b/src/gui/rss/rss_imp.cpp @@ -44,11 +44,10 @@ #include "cookiesdlg.h" #include "base/preferences.h" #include "rsssettingsdlg.h" -#include "rssmanager.h" -#include "rssfolder.h" -#include "rssarticle.h" -#include "rssparser.h" -#include "rssfeed.h" +#include "base/rss/rssmanager.h" +#include "base/rss/rssfolder.h" +#include "base/rss/rssarticle.h" +#include "base/rss/rssfeed.h" #include "automatedrssdownloader.h" #include "guiiconprovider.h" #include "autoexpandabledialog.h" @@ -79,7 +78,7 @@ void RSSImp::displayRSSListMenu(const QPoint& pos) myRSSListMenu.addAction(actionMark_items_read); myRSSListMenu.addSeparator(); if (selectedItems.size() == 1) { - if (m_feedList->getRSSItem(selectedItems.first()) != m_rssManager) { + if (m_feedList->getRSSItem(selectedItems.first()) != m_rssManager->rootFolder()) { myRSSListMenu.addAction(actionRename); myRSSListMenu.addAction(actionDelete); myRSSListMenu.addSeparator(); @@ -119,9 +118,9 @@ void RSSImp::displayItemsListMenu(const QPoint&) bool hasLink = false; foreach (const QListWidgetItem* item, selectedItems) { if (!item) continue; - RssFeedPtr feed = m_feedList->getRSSItemFromUrl(item->data(Article::FeedUrlRole).toString()); + Rss::FeedPtr feed = m_feedList->getRSSItemFromUrl(item->data(Article::FeedUrlRole).toString()); if (!feed) continue; - RssArticlePtr article = feed->getItem(item->data(Article::IdRole).toString()); + Rss::ArticlePtr article = feed->getItem(item->data(Article::IdRole).toString()); if (!article) continue; if (!article->torrentUrl().isEmpty()) @@ -161,21 +160,22 @@ void RSSImp::on_actionManage_cookies_triggered() void RSSImp::askNewFolder() { QTreeWidgetItem* parent_item = 0; - RssFolderPtr rss_parent; + Rss::FolderPtr rss_parent; if (m_feedList->selectedItems().size() > 0) { parent_item = m_feedList->selectedItems().at(0); - rss_parent = qSharedPointerDynamicCast(m_feedList->getRSSItem(parent_item)); + rss_parent = qSharedPointerDynamicCast(m_feedList->getRSSItem(parent_item)); Q_ASSERT(rss_parent); } else { - rss_parent = m_rssManager; + rss_parent = m_rssManager->rootFolder(); } bool ok; QString new_name = AutoExpandableDialog::getText(this, tr("Please choose a folder name"), tr("Folder name:"), QLineEdit::Normal, tr("New folder"), &ok); - if (!ok) + if (!ok || rss_parent->hasChild(new_name)) return; - RssFolderPtr newFolder = rss_parent->addFolder(new_name); + Rss::FolderPtr newFolder(new Rss::Folder(new_name)); + rss_parent->addFile(newFolder); QTreeWidgetItem* folderItem = createFolderListItem(newFolder); if (parent_item) parent_item->addChild(folderItem); @@ -204,11 +204,11 @@ void RSSImp::on_newFeedButton_clicked() if (!m_feedList->isFolder(parent_item)) parent_item = parent_item->parent(); } - RssFolderPtr rss_parent; + Rss::FolderPtr rss_parent; if (parent_item) - rss_parent = qSharedPointerCast(m_feedList->getRSSItem(parent_item)); + rss_parent = qSharedPointerCast(m_feedList->getRSSItem(parent_item)); else - rss_parent = m_rssManager; + rss_parent = m_rssManager->rootFolder(); // Ask for feed URL bool ok; QString clip_txt = qApp->clipboard()->text(); @@ -230,7 +230,9 @@ void RSSImp::on_newFeedButton_clicked() QMessageBox::Ok); return; } - RssFeedPtr stream = rss_parent->addStream(m_rssManager.data(), newUrl); + + Rss::FeedPtr stream(new Rss::Feed(newUrl, m_rssManager.data())); + rss_parent->addFile(stream); // Create TreeWidget item QTreeWidgetItem* item = createFolderListItem(stream); if (parent_item) @@ -261,22 +263,18 @@ void RSSImp::deleteSelectedItems() foreach (QTreeWidgetItem* item, selectedItems) { if (item == m_feedList->stickyUnreadItem()) continue; - RssFilePtr rss_item = m_feedList->getRSSItem(item); + Rss::FilePtr rss_item = m_feedList->getRSSItem(item); QTreeWidgetItem* parent = item->parent(); // Notify TreeWidget m_feedList->itemAboutToBeRemoved(item); // Actually delete the item - rss_item->parent()->removeChild(rss_item->id()); + rss_item->parentFolder()->removeChild(rss_item->id()); delete item; // Update parents count - while (parent && parent != m_feedList->invisibleRootItem()) { - updateItemInfos (parent); + while (parent && (parent != m_feedList->invisibleRootItem())) { + updateItemInfos(parent); parent = parent->parent(); } - // Clear feed data from RSS parser (possible caching). - RssFeed* rssFeed = dynamic_cast(rss_item.data()); - if (rssFeed) - m_rssManager->rssParser()->clearFeedData(rssFeed->url()); } m_rssManager->saveStreamList(); // Update Unread items @@ -342,9 +340,9 @@ void RSSImp::downloadSelectedTorrents() return; foreach (QListWidgetItem* item, selected_items) { if (!item) continue; - RssFeedPtr feed = m_feedList->getRSSItemFromUrl(item->data(Article::FeedUrlRole).toString()); + Rss::FeedPtr feed = m_feedList->getRSSItemFromUrl(item->data(Article::FeedUrlRole).toString()); if (!feed) continue; - RssArticlePtr article = feed->getItem(item->data(Article::IdRole).toString()); + Rss::ArticlePtr article = feed->getItem(item->data(Article::IdRole).toString()); if (!article) continue; // Mark as read @@ -372,9 +370,9 @@ void RSSImp::openSelectedArticlesUrls() return; foreach (QListWidgetItem* item, selected_items) { if (!item) continue; - RssFeedPtr feed = m_feedList->getRSSItemFromUrl(item->data(Article::FeedUrlRole).toString()); + Rss::FeedPtr feed = m_feedList->getRSSItemFromUrl(item->data(Article::FeedUrlRole).toString()); if (!feed) continue; - RssArticlePtr article = feed->getItem(item->data(Article::IdRole).toString()); + Rss::ArticlePtr article = feed->getItem(item->data(Article::IdRole).toString()); if (!article) continue; // Mark as read @@ -400,14 +398,14 @@ void RSSImp::renameSelectedRssFile() QTreeWidgetItem* item = selectedItems.first(); if (item == m_feedList->stickyUnreadItem()) return; - RssFilePtr rss_item = m_feedList->getRSSItem(item); + Rss::FilePtr rss_item = m_feedList->getRSSItem(item); bool ok; QString newName; do { newName = AutoExpandableDialog::getText(this, tr("Please choose a new name for this RSS feed"), tr("New feed name:"), QLineEdit::Normal, m_feedList->getRSSItem(item)->displayName(), &ok); // Check if name is already taken if (ok) { - if (rss_item->parent()->hasChild(newName)) { + if (rss_item->parentFolder()->hasChild(newName)) { QMessageBox::warning(0, tr("Name already in use"), tr("This name is already used by another item, please choose another one.")); ok = false; } @@ -427,7 +425,7 @@ void RSSImp::refreshSelectedItems() { QList selectedItems = m_feedList->selectedItems(); foreach (QTreeWidgetItem* item, selectedItems) { - RssFilePtr file = m_feedList->getRSSItem(item); + Rss::FilePtr file = m_feedList->getRSSItem(item); // Update icons if (item == m_feedList->stickyUnreadItem()) { refreshAllFeeds(); @@ -437,10 +435,10 @@ void RSSImp::refreshSelectedItems() if (!file->refresh()) continue; // Update UI - if (qSharedPointerDynamicCast(file)) { + if (qSharedPointerDynamicCast(file)) { item->setData(0, Qt::DecorationRole, QVariant(QIcon(":/icons/loading.png"))); } - else if (qSharedPointerDynamicCast(file)) { + else if (qSharedPointerDynamicCast(file)) { // Update feeds in the folder foreach (QTreeWidgetItem *feed, m_feedList->getAllFeedItems(item)) feed->setData(0, Qt::DecorationRole, QVariant(QIcon(":/icons/loading.png"))); @@ -464,7 +462,7 @@ void RSSImp::on_markReadButton_clicked() { QList selectedItems = m_feedList->selectedItems(); foreach (QTreeWidgetItem* item, selectedItems) { - RssFilePtr rss_item = m_feedList->getRSSItem(item); + Rss::FilePtr rss_item = m_feedList->getRSSItem(item); Q_ASSERT(rss_item); rss_item->markAsRead(); updateItemInfos(item); @@ -474,24 +472,24 @@ void RSSImp::on_markReadButton_clicked() populateArticleList(m_feedList->currentItem()); } -QTreeWidgetItem* RSSImp::createFolderListItem(const RssFilePtr& rssFile) +QTreeWidgetItem* RSSImp::createFolderListItem(const Rss::FilePtr& rssFile) { Q_ASSERT(rssFile); QTreeWidgetItem* item = new QTreeWidgetItem; item->setData(0, Qt::DisplayRole, QVariant(rssFile->displayName() + QString::fromUtf8(" (") + QString::number(rssFile->unreadCount()) + QString(")"))); - item->setData(0, Qt::DecorationRole, rssFile->icon()); + item->setData(0, Qt::DecorationRole, QIcon(rssFile->iconPath())); return item; } -void RSSImp::fillFeedsList(QTreeWidgetItem* parent, const RssFolderPtr& rss_parent) +void RSSImp::fillFeedsList(QTreeWidgetItem* parent, const Rss::FolderPtr& rss_parent) { - QList children; + QList children; if (parent) children = rss_parent->getContent(); else - children = m_rssManager->getContent(); - foreach (const RssFilePtr& rssFile, children) { + children = m_rssManager->rootFolder()->getContent(); + foreach (const Rss::FilePtr& rssFile, children) { QTreeWidgetItem* item = createFolderListItem(rssFile); Q_ASSERT(item); if (parent) @@ -503,12 +501,12 @@ void RSSImp::fillFeedsList(QTreeWidgetItem* parent, const RssFolderPtr& rss_pare m_feedList->itemAdded(item, rssFile); // Recursive call if this is a folder. - if (RssFolderPtr folder = qSharedPointerDynamicCast(rssFile)) + if (Rss::FolderPtr folder = qSharedPointerDynamicCast(rssFile)) fillFeedsList(item, folder); } } -QListWidgetItem* RSSImp::createArticleListItem(const RssArticlePtr& article) +QListWidgetItem* RSSImp::createArticleListItem(const Rss::ArticlePtr& article) { Q_ASSERT(article); QListWidgetItem* item = new QListWidgetItem; @@ -536,7 +534,7 @@ void RSSImp::populateArticleList(QTreeWidgetItem* item) return; } - RssFilePtr rss_item = m_feedList->getRSSItem(item); + Rss::FilePtr rss_item = m_feedList->getRSSItem(item); if (!rss_item) return; @@ -546,14 +544,14 @@ void RSSImp::populateArticleList(QTreeWidgetItem* item) listArticles->clear(); qDebug("Getting the list of news"); - RssArticleList articles; - if (rss_item == m_rssManager) + Rss::ArticleList articles; + if (rss_item == m_rssManager->rootFolder()) articles = rss_item->unreadArticleListByDateDesc(); else articles = rss_item->articleListByDateDesc(); qDebug("Got the list of news"); - foreach (const RssArticlePtr& article, articles) { + foreach (const Rss::ArticlePtr& article, articles) { QListWidgetItem* articleItem = createArticleListItem(article); listArticles->addItem(articleItem); } @@ -570,9 +568,9 @@ void RSSImp::refreshTextBrowser() if (item == m_currentArticle) return; m_currentArticle = item; - RssFeedPtr feed = m_feedList->getRSSItemFromUrl(item->data(Article::FeedUrlRole).toString()); + Rss::FeedPtr feed = m_feedList->getRSSItemFromUrl(item->data(Article::FeedUrlRole).toString()); if (!feed) return; - RssArticlePtr article = feed->getItem(item->data(Article::IdRole).toString()); + Rss::ArticlePtr article = feed->getItem(item->data(Article::IdRole).toString()); if (!article) return; QString html; html += "
"; @@ -651,12 +649,12 @@ void RSSImp::updateItemsInfos(const QList& items) void RSSImp::updateItemInfos(QTreeWidgetItem *item) { - RssFilePtr rss_item = m_feedList->getRSSItem(item); + Rss::FilePtr rss_item = m_feedList->getRSSItem(item); if (!rss_item) return; QString name; - if (rss_item == m_rssManager) { + if (rss_item == m_rssManager->rootFolder()) { name = tr("Unread"); emit updateRSSCount(rss_item->unreadCount()); } @@ -678,10 +676,10 @@ void RSSImp::updateFeedInfos(const QString& url, const QString& display_name, ui { qDebug() << Q_FUNC_INFO << display_name; QTreeWidgetItem *item = m_feedList->getTreeItemFromUrl(url); - RssFeedPtr stream = qSharedPointerCast(m_feedList->getRSSItem(item)); + Rss::FeedPtr stream = qSharedPointerCast(m_feedList->getRSSItem(item)); item->setText(0, display_name + QString::fromUtf8(" (") + QString::number(nbUnread) + QString(")")); if (!stream->isLoading()) - item->setData(0, Qt::DecorationRole, QVariant(stream->icon())); + item->setData(0, Qt::DecorationRole, QIcon(stream->iconPath())); // Update parent if (item->parent()) updateItemInfos(item->parent()); @@ -708,7 +706,7 @@ void RSSImp::updateRefreshInterval(uint val) RSSImp::RSSImp(QWidget *parent): QWidget(parent), - m_rssManager(new RssManager) + m_rssManager(new Rss::Manager) { setupUi(this); // Icons @@ -800,7 +798,7 @@ void RSSImp::on_rssDownloaderBtn_clicked() AutomatedRssDownloader dlg(m_rssManager, this); dlg.exec(); if (dlg.isRssDownloaderEnabled()) { - m_rssManager->recheckRssItemsForDownload(); + m_rssManager->rootFolder()->recheckRssItemsForDownload(); refreshAllFeeds(); } } diff --git a/src/gui/rss/rss_imp.h b/src/gui/rss/rss_imp.h index 5754effd6..5395ef9f8 100644 --- a/src/gui/rss/rss_imp.h +++ b/src/gui/rss/rss_imp.h @@ -35,9 +35,9 @@ #include #include +#include "base/rss/rssfolder.h" +#include "base/rss/rssmanager.h" #include "ui_rss.h" -#include "rssfolder.h" -#include "rssmanager.h" class FeedListWidget; @@ -78,7 +78,7 @@ private slots: void updateItemInfos(QTreeWidgetItem *item); void openSelectedArticlesUrls(); void downloadSelectedTorrents(); - void fillFeedsList(QTreeWidgetItem *parent = 0, const RssFolderPtr& rss_parent = RssFolderPtr()); + void fillFeedsList(QTreeWidgetItem *parent = 0, const Rss::FolderPtr& rss_parent = Rss::FolderPtr()); void saveSlidersPosition(); void restoreSlidersPosition(); void askNewFolder(); @@ -89,11 +89,11 @@ private slots: void on_rssDownloaderBtn_clicked(); private: - static QListWidgetItem* createArticleListItem(const RssArticlePtr& article); - static QTreeWidgetItem* createFolderListItem(const RssFilePtr& rssFile); + static QListWidgetItem* createArticleListItem(const Rss::ArticlePtr& article); + static QTreeWidgetItem* createFolderListItem(const Rss::FilePtr& rssFile); private: - RssManagerPtr m_rssManager; + Rss::ManagerPtr m_rssManager; FeedListWidget *m_feedList; QListWidgetItem* m_currentArticle; QShortcut *editHotkey; diff --git a/src/gui/rss/rssarticle.cpp b/src/gui/rss/rssarticle.cpp deleted file mode 100644 index fbc7218bf..000000000 --- a/src/gui/rss/rssarticle.cpp +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * In addition, as a special exception, the copyright holders give permission to - * link this program with the OpenSSL project's "OpenSSL" library (or with - * modified versions of it that use the same license as the "OpenSSL" library), - * and distribute the linked executables. You must obey the GNU General Public - * License in all respects for all of the code used other than "OpenSSL". If you - * modify file(s), you may extend this exception to your version of the file(s), - * but you are not obligated to do so. If you do not wish to do so, delete this - * exception statement from your version. - * - * Contact: chris@qbittorrent.org, arnaud@qbittorrent.org - */ - -#include -#include -#include - -#include "rssarticle.h" -#include "rssfeed.h" - -// public constructor -RssArticle::RssArticle(RssFeed* parent, const QString& guid): - m_parent(parent), m_guid(guid), m_read(false) {} - -bool RssArticle::hasAttachment() const { - return !m_torrentUrl.isEmpty(); -} - -QVariantHash RssArticle::toHash() const { - QVariantHash item; - item["title"] = m_title; - item["id"] = m_guid; - item["torrent_url"] = m_torrentUrl; - item["news_link"] = m_link; - item["description"] = m_description; - item["date"] = m_date; - item["author"] = m_author; - item["read"] = m_read; - return item; -} - -RssArticlePtr hashToRssArticle(RssFeed* parent, const QVariantHash& h) { - const QString guid = h.value("id").toString(); - if (guid.isEmpty()) - return RssArticlePtr(); - - RssArticlePtr art(new RssArticle(parent, guid)); - art->m_title = h.value("title", "").toString(); - art->m_torrentUrl = h.value("torrent_url", "").toString(); - art->m_link = h.value("news_link", "").toString(); - art->m_description = h.value("description").toString(); - art->m_date = h.value("date").toDateTime(); - art->m_author = h.value("author").toString(); - art->m_read = h.value("read", false).toBool(); - - return art; -} - -RssFeed* RssArticle::parent() const { - return m_parent; -} - -const QString& RssArticle::author() const { - return m_author; -} - -const QString& RssArticle::torrentUrl() const { - return m_torrentUrl; -} - -const QString& RssArticle::link() const { - return m_link; -} - -QString RssArticle::description() const -{ - return m_description.isNull() ? "" : m_description; -} - -const QDateTime& RssArticle::date() const { - return m_date; -} - -bool RssArticle::isRead() const { - return m_read; -} - -void RssArticle::markAsRead() { - if (m_read) - return; - - m_read = true; - m_parent->decrementUnreadCount(); - m_parent->markAsDirty(); - emit articleWasRead(); -} - -const QString& RssArticle::guid() const -{ - return m_guid; -} - -const QString& RssArticle::title() const -{ - return m_title; -} - -void RssArticle::handleTorrentDownloadSuccess(const QString &url) { - if (url == m_torrentUrl) - markAsRead(); -} diff --git a/src/gui/rss/rssdownloadrule.cpp b/src/gui/rss/rssdownloadrule.cpp deleted file mode 100644 index cd37a3a56..000000000 --- a/src/gui/rss/rssdownloadrule.cpp +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2010 Christophe Dumez - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * In addition, as a special exception, the copyright holders give permission to - * link this program with the OpenSSL project's "OpenSSL" library (or with - * modified versions of it that use the same license as the "OpenSSL" library), - * and distribute the linked executables. You must obey the GNU General Public - * License in all respects for all of the code used other than "OpenSSL". If you - * modify file(s), you may extend this exception to your version of the file(s), - * but you are not obligated to do so. If you do not wish to do so, delete this - * exception statement from your version. - * - * Contact : chris@qbittorrent.org - */ - -#include -#include -#include - -#include "rssdownloadrule.h" -#include "base/preferences.h" -#include "rssfeed.h" -#include "rssarticle.h" -#include "base/utils/fs.h" - -RssDownloadRule::RssDownloadRule(): m_enabled(false), m_useRegex(false), m_apstate(USE_GLOBAL) -{ -} - -bool RssDownloadRule::matches(const QString &article_title) const -{ - foreach (const QString& token, m_mustContain) { - if (!token.isEmpty()) { - QRegExp reg(token, Qt::CaseInsensitive, m_useRegex ? QRegExp::RegExp : QRegExp::Wildcard); - if (reg.indexIn(article_title) < 0) - 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; - } - } - if (!m_episodeFilter.isEmpty()) { - qDebug("Checking episode filter"); - QRegExp f("(^\\d{1,4})x(.*;$)"); - int pos = f.indexIn(m_episodeFilter); - if (pos < 0) - 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('-'); - 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(article_title); - 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(article_title) != -1) - return true; - } - } - return false; - } - return true; -} - -void RssDownloadRule::setMustContain(const QString &tokens) -{ - if (m_useRegex) - m_mustContain = QStringList() << tokens; - else - m_mustContain = tokens.split(" "); -} - -void RssDownloadRule::setMustNotContain(const QString &tokens) -{ - if (m_useRegex) - m_mustNotContain = QStringList() << tokens; - else - m_mustNotContain = tokens.split("|"); -} - -RssDownloadRulePtr RssDownloadRule::fromVariantHash(const QVariantHash &rule_hash) -{ - RssDownloadRulePtr rule(new RssDownloadRule); - rule->setName(rule_hash.value("name").toString()); - rule->setUseRegex(rule_hash.value("use_regex", false).toBool()); - rule->setMustContain(rule_hash.value("must_contain").toString()); - rule->setMustNotContain(rule_hash.value("must_not_contain").toString()); - rule->setEpisodeFilter(rule_hash.value("episode_filter").toString()); - rule->setRssFeeds(rule_hash.value("affected_feeds").toStringList()); - rule->setEnabled(rule_hash.value("enabled", false).toBool()); - rule->setSavePath(rule_hash.value("save_path").toString()); - rule->setLabel(rule_hash.value("label_assigned").toString()); - rule->setAddPaused(AddPausedState(rule_hash.value("add_paused").toUInt())); - rule->setLastMatch(rule_hash.value("last_match").toDateTime()); - rule->setIgnoreDays(rule_hash.value("ignore_days").toInt()); - return rule; -} - -QVariantHash RssDownloadRule::toVariantHash() const -{ - QVariantHash hash; - hash["name"] = m_name; - hash["must_contain"] = m_mustContain.join(" "); - hash["must_not_contain"] = m_mustNotContain.join("|"); - hash["save_path"] = m_savePath; - hash["affected_feeds"] = m_rssFeeds; - hash["enabled"] = m_enabled; - hash["label_assigned"] = m_label; - hash["use_regex"] = m_useRegex; - hash["add_paused"] = m_apstate; - hash["episode_filter"] = m_episodeFilter; - hash["last_match"] = m_lastMatch; - hash["ignore_days"] = m_ignoreDays; - return hash; -} - -bool RssDownloadRule::operator==(const RssDownloadRule &other) const { - return m_name == other.name(); -} - -void RssDownloadRule::setSavePath(const QString &save_path) -{ - if (!save_path.isEmpty() && QDir(save_path) != QDir(Preferences::instance()->getSavePath())) - m_savePath = Utils::Fs::fromNativePath(save_path); - else - m_savePath = QString(); -} - -QStringList RssDownloadRule::findMatchingArticles(const RssFeedPtr& feed) const -{ - QStringList ret; - const RssArticleHash& feed_articles = feed->articleHash(); - - RssArticleHash::ConstIterator artIt = feed_articles.begin(); - RssArticleHash::ConstIterator artItend = feed_articles.end(); - for ( ; artIt != artItend ; ++artIt) { - const QString title = artIt.value()->title(); - if (matches(title)) - ret << title; - } - return ret; -} diff --git a/src/gui/rss/rssdownloadrule.h b/src/gui/rss/rssdownloadrule.h deleted file mode 100644 index c76bd3187..000000000 --- a/src/gui/rss/rssdownloadrule.h +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2010 Christophe Dumez - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * In addition, as a special exception, the copyright holders give permission to - * link this program with the OpenSSL project's "OpenSSL" library (or with - * modified versions of it that use the same license as the "OpenSSL" library), - * and distribute the linked executables. You must obey the GNU General Public - * License in all respects for all of the code used other than "OpenSSL". If you - * modify file(s), you may extend this exception to your version of the file(s), - * but you are not obligated to do so. If you do not wish to do so, delete this - * exception statement from your version. - * - * Contact : chris@qbittorrent.org - */ - -#ifndef RSSDOWNLOADRULE_H -#define RSSDOWNLOADRULE_H - -#include -#include -#include -#include - -class RssFeed; -typedef QSharedPointer RssFeedPtr; - -class RssDownloadRule; -typedef QSharedPointer RssDownloadRulePtr; - -class RssDownloadRule -{ - -public: - enum AddPausedState { - USE_GLOBAL = 0, - ALWAYS_PAUSED, - NEVER_PAUSED - }; - - explicit RssDownloadRule(); - static RssDownloadRulePtr fromVariantHash(const QVariantHash &rule_hash); - QVariantHash toVariantHash() const; - bool matches(const QString &article_title) const; - void setMustContain(const QString &tokens); - void setMustNotContain(const QString &tokens); - inline QStringList rssFeeds() const { return m_rssFeeds; } - inline void setRssFeeds(const QStringList& rss_feeds) { m_rssFeeds = rss_feeds; } - inline QString name() const { return m_name; } - inline void setName(const QString &name) { m_name = name; } - inline QString savePath() const { return m_savePath; } - void setSavePath(const QString &save_path); - inline AddPausedState addPaused() const { return m_apstate; } - inline void setAddPaused(const AddPausedState &aps) { m_apstate = aps; } - inline QString label() const { return m_label; } - inline void setLabel(const QString &_label) { m_label = _label; } - inline bool isEnabled() const { return m_enabled; } - inline void setEnabled(bool enable) { m_enabled = enable; } - inline void setLastMatch(const QDateTime& d) { m_lastMatch = d; } - inline QDateTime lastMatch() const { return m_lastMatch; } - inline void setIgnoreDays(int d) { m_ignoreDays = d; } - inline int ignoreDays() const { return m_ignoreDays; } - inline QString mustContain() const { return m_mustContain.join(" "); } - inline QString mustNotContain() const { return m_mustNotContain.join("|"); } - inline bool useRegex() const { return m_useRegex; } - inline void setUseRegex(bool enabled) { m_useRegex = enabled; } - inline QString episodeFilter() const { return m_episodeFilter; } - inline void setEpisodeFilter(const QString& e) { m_episodeFilter = e; } - QStringList findMatchingArticles(const RssFeedPtr& feed) const; - // Operators - bool operator==(const RssDownloadRule &other) const; - -private: - QString m_name; - QStringList m_mustContain; - QStringList m_mustNotContain; - QString m_episodeFilter; - QString m_savePath; - QString m_label; - bool m_enabled; - QStringList m_rssFeeds; - bool m_useRegex; - AddPausedState m_apstate; - QDateTime m_lastMatch; - int m_ignoreDays; -}; - -#endif // RSSDOWNLOADRULE_H diff --git a/src/gui/rss/rssdownloadrulelist.cpp b/src/gui/rss/rssdownloadrulelist.cpp deleted file mode 100644 index 12f6a9710..000000000 --- a/src/gui/rss/rssdownloadrulelist.cpp +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2010 Christophe Dumez - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * In addition, as a special exception, the copyright holders give permission to - * link this program with the OpenSSL project's "OpenSSL" library (or with - * modified versions of it that use the same license as the "OpenSSL" library), - * and distribute the linked executables. You must obey the GNU General Public - * License in all respects for all of the code used other than "OpenSSL". If you - * modify file(s), you may extend this exception to your version of the file(s), - * but you are not obligated to do so. If you do not wish to do so, delete this - * exception statement from your version. - * - * Contact : chris@qbittorrent.org - */ - -#include -#include -#include - -#include "rssdownloadrulelist.h" -#include "base/preferences.h" -#include "base/qinisettings.h" - -RssDownloadRuleList::RssDownloadRuleList() -{ - loadRulesFromStorage(); -} - -RssDownloadRulePtr RssDownloadRuleList::findMatchingRule(const QString &feed_url, const QString &article_title) const -{ - Q_ASSERT(Preferences::instance()->isRssDownloadingEnabled()); - QStringList rule_names = m_feedRules.value(feed_url); - foreach (const QString &rule_name, rule_names) { - RssDownloadRulePtr rule = m_rules[rule_name]; - if (rule->isEnabled() && rule->matches(article_title)) return rule; - } - return RssDownloadRulePtr(); -} - -void RssDownloadRuleList::replace(RssDownloadRuleList *other) { - m_rules.clear(); - m_feedRules.clear(); - foreach (const QString& name, other->ruleNames()) { - saveRule(other->getRule(name)); - } -} - -void RssDownloadRuleList::saveRulesToStorage() -{ - QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss"); - qBTRSS.setValue("download_rules", toVariantHash()); -} - -void RssDownloadRuleList::loadRulesFromStorage() -{ - QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss"); - loadRulesFromVariantHash(qBTRSS.value("download_rules").toHash()); -} - -QVariantHash RssDownloadRuleList::toVariantHash() const -{ - QVariantHash ret; - foreach (const RssDownloadRulePtr &rule, m_rules.values()) { - ret.insert(rule->name(), rule->toVariantHash()); - } - return ret; -} - -void RssDownloadRuleList::loadRulesFromVariantHash(const QVariantHash &h) -{ - QVariantHash::ConstIterator it = h.begin(); - QVariantHash::ConstIterator itend = h.end(); - for ( ; it != itend; ++it) { - RssDownloadRulePtr rule = RssDownloadRule::fromVariantHash(it.value().toHash()); - if (rule && !rule->name().isEmpty()) - saveRule(rule); - } -} - -void RssDownloadRuleList::saveRule(const RssDownloadRulePtr &rule) -{ - qDebug() << Q_FUNC_INFO << rule->name(); - Q_ASSERT(rule); - if (m_rules.contains(rule->name())) { - qDebug("This is an update, removing old rule first"); - removeRule(rule->name()); - } - m_rules.insert(rule->name(), rule); - // Update feedRules hashtable - foreach (const QString &feed_url, rule->rssFeeds()) { - m_feedRules[feed_url].append(rule->name()); - } - qDebug() << Q_FUNC_INFO << "EXIT"; -} - -void RssDownloadRuleList::removeRule(const QString &name) -{ - qDebug() << Q_FUNC_INFO << name; - if (!m_rules.contains(name)) return; - RssDownloadRulePtr rule = m_rules.take(name); - // Update feedRules hashtable - foreach (const QString &feed_url, rule->rssFeeds()) { - m_feedRules[feed_url].removeOne(rule->name()); - } -} - -void RssDownloadRuleList::renameRule(const QString &old_name, const QString &new_name) -{ - if (!m_rules.contains(old_name)) return; - RssDownloadRulePtr rule = m_rules.take(old_name); - rule->setName(new_name); - m_rules.insert(new_name, rule); - // Update feedRules hashtable - foreach (const QString &feed_url, rule->rssFeeds()) { - m_feedRules[feed_url].replace(m_feedRules[feed_url].indexOf(old_name), new_name); - } -} - -RssDownloadRulePtr RssDownloadRuleList::getRule(const QString &name) const -{ - return m_rules.value(name); -} - -bool RssDownloadRuleList::serialize(const QString& path) -{ - QFile f(path); - 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) -{ - 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; - } -} - diff --git a/src/gui/rss/rssfeed.cpp b/src/gui/rss/rssfeed.cpp deleted file mode 100644 index 7659ecae1..000000000 --- a/src/gui/rss/rssfeed.cpp +++ /dev/null @@ -1,448 +0,0 @@ -/* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * In addition, as a special exception, the copyright holders give permission to - * link this program with the OpenSSL project's "OpenSSL" library (or with - * modified versions of it that use the same license as the "OpenSSL" library), - * and distribute the linked executables. You must obey the GNU General Public - * License in all respects for all of the code used other than "OpenSSL". If you - * modify file(s), you may extend this exception to your version of the file(s), - * but you are not obligated to do so. If you do not wish to do so, delete this - * exception statement from your version. - * - * Contact: chris@qbittorrent.org, arnaud@qbittorrent.org - */ - -#include -#include "rssfeed.h" -#include "rssmanager.h" -#include "base/bittorrent/session.h" -#include "base/bittorrent/magneturi.h" -#include "rssfolder.h" -#include "base/preferences.h" -#include "base/qinisettings.h" -#include "rssarticle.h" -#include "rssparser.h" -#include "base/utils/misc.h" -#include "rssdownloadrulelist.h" -#include "base/net/downloadmanager.h" -#include "base/net/downloadhandler.h" -#include "base/utils/fs.h" -#include "base/logger.h" - -bool rssArticleDateRecentThan(const RssArticlePtr& left, const RssArticlePtr& right) -{ - return left->date() > right->date(); -} - -RssFeed::RssFeed(RssManager* manager, RssFolder* parent, const QString& url): - m_manager(manager), - m_parent(parent), - m_url (QUrl::fromEncoded(url.toUtf8()).toString()), - m_icon(":/icons/oxygen/application-rss+xml.png"), - m_unreadCount(0), - m_dirty(false), - m_inErrorState(false), - m_loading(false) -{ - qDebug() << Q_FUNC_INFO << m_url; - // Listen for new RSS downloads - connect(manager->rssParser(), SIGNAL(feedTitle(QString,QString)), SLOT(handleFeedTitle(QString,QString))); - connect(manager->rssParser(), SIGNAL(newArticle(QString,QVariantHash)), SLOT(handleNewArticle(QString,QVariantHash))); - connect(manager->rssParser(), SIGNAL(feedParsingFinished(QString,QString)), SLOT(handleFeedParsingFinished(QString,QString))); - - // Download the RSS Feed icon - Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(iconUrl(), true); - connect(handler, SIGNAL(downloadFinished(QString, QString)), this, SLOT(handleFinishedDownload(QString, QString))); - connect(handler, SIGNAL(downloadFailed(QString, QString)), this, SLOT(handleDownloadFailure(QString, QString))); - m_iconUrl = handler->url(); - - // Load old RSS articles - loadItemsFromDisk(); -} - -RssFeed::~RssFeed() -{ - if (!m_icon.startsWith(":/") && QFile::exists(m_icon)) - Utils::Fs::forceRemove(m_icon); -} - -void RssFeed::saveItemsToDisk() -{ - qDebug() << Q_FUNC_INFO << m_url; - if (!m_dirty) - return; - markAsDirty(false); - - QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss"); - QVariantList old_items; - - RssArticleHash::ConstIterator it = m_articles.begin(); - RssArticleHash::ConstIterator itend = m_articles.end(); - for ( ; it != itend; ++it) { - old_items << it.value()->toHash(); - } - qDebug("Saving %d old items for feed %s", old_items.size(), qPrintable(displayName())); - QHash all_old_items = qBTRSS.value("old_items", QHash()).toHash(); - all_old_items[m_url] = old_items; - qBTRSS.setValue("old_items", all_old_items); -} - -void RssFeed::loadItemsFromDisk() -{ - QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss"); - QHash all_old_items = qBTRSS.value("old_items", QHash()).toHash(); - const QVariantList old_items = all_old_items.value(m_url, QVariantList()).toList(); - qDebug("Loading %d old items for feed %s", old_items.size(), qPrintable(displayName())); - - foreach (const QVariant& var_it, old_items) { - QVariantHash item = var_it.toHash(); - RssArticlePtr rss_item = hashToRssArticle(this, item); - if (rss_item) - addArticle(rss_item); - } -} - -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 - if (Preferences::instance()->isRssDownloadingEnabled()) { - if (lbIndex < max_articles && !article->isRead()) - downloadArticleTorrentIfMatching(m_manager->downloadRules(), article); - } - } - else { - // m_articles.contains(article->guid()) - // Try to download skipped articles - if (Preferences::instance()->isRssDownloadingEnabled()) { - RssArticlePtr skipped = m_articles.value(article->guid(), RssArticlePtr()); - if (skipped) { - if (!skipped->isRead()) - downloadArticleTorrentIfMatching(m_manager->downloadRules(), skipped); - } - } - } -} - -bool RssFeed::refresh() -{ - if (m_loading) { - qWarning() << Q_FUNC_INFO << "Feed" << displayName() << "is already being refreshed, ignoring request"; - return false; - } - m_loading = true; - // Download the RSS again - Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(m_url, true); - connect(handler, SIGNAL(downloadFinished(QString, QString)), this, SLOT(handleFinishedDownload(QString, QString))); - connect(handler, SIGNAL(downloadFailed(QString, QString)), this, SLOT(handleDownloadFailure(QString, QString))); - m_url = handler->url(); // sync URL encoding - return true; -} - -void RssFeed::removeAllSettings() -{ - qDebug() << "Removing all settings / history for feed: " << m_url; - QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss"); - QVariantHash feeds_w_downloader = qBTRSS.value("downloader_on", QVariantHash()).toHash(); - if (feeds_w_downloader.contains(m_url)) { - feeds_w_downloader.remove(m_url); - qBTRSS.setValue("downloader_on", feeds_w_downloader); - } - QVariantHash all_feeds_filters = qBTRSS.value("feed_filters", QVariantHash()).toHash(); - if (all_feeds_filters.contains(m_url)) { - all_feeds_filters.remove(m_url); - qBTRSS.setValue("feed_filters", all_feeds_filters); - } - QVariantHash all_old_items = qBTRSS.value("old_items", QVariantHash()).toHash(); - if (all_old_items.contains(m_url)) { - all_old_items.remove(m_url); - qBTRSS.setValue("old_items", all_old_items); - } -} - -bool RssFeed::isLoading() const -{ - return m_loading; -} - -QString RssFeed::title() const -{ - return m_title; -} - -void RssFeed::rename(const QString &new_name) -{ - qDebug() << "Renaming stream to" << new_name; - m_alias = new_name; -} - -// Return the alias if the stream has one, the url if it has no alias -QString RssFeed::displayName() const -{ - if (!m_alias.isEmpty()) - return m_alias; - if (!m_title.isEmpty()) - return m_title; - return m_url; -} - -QString RssFeed::url() const -{ - return m_url; -} - -QIcon RssFeed::icon() const -{ - if (m_inErrorState) - return QIcon(":/icons/oxygen/unavailable.png"); - - return QIcon(m_icon); -} - -bool RssFeed::hasCustomIcon() const -{ - return !m_icon.startsWith(":/"); -} - -void RssFeed::setIconPath(const QString& path) -{ - if (path.isEmpty() || !QFile::exists(path)) - return; - - m_icon = path; -} - -RssArticlePtr RssFeed::getItem(const QString& guid) const -{ - return m_articles.value(guid); -} - -uint RssFeed::count() const -{ - return m_articles.size(); -} - -void RssFeed::markAsRead() -{ - RssArticleHash::ConstIterator it = m_articles.begin(); - RssArticleHash::ConstIterator itend = m_articles.end(); - for ( ; it != itend; ++it) { - it.value()->markAsRead(); - } - m_unreadCount = 0; - m_manager->forwardFeedInfosChanged(m_url, displayName(), 0); -} - -void RssFeed::markAsDirty(bool dirty) -{ - m_dirty = dirty; -} - -uint RssFeed::unreadCount() const -{ - return m_unreadCount; -} - -RssArticleList RssFeed::articleListByDateDesc() const -{ - return m_articlesByDate; -} - -RssArticleList RssFeed::unreadArticleListByDateDesc() const -{ - RssArticleList unread_news; - - RssArticleList::ConstIterator it = m_articlesByDate.begin(); - RssArticleList::ConstIterator itend = m_articlesByDate.end(); - for ( ; it != itend; ++it) { - if (!(*it)->isRead()) - unread_news << *it; - } - return unread_news; -} - -// download the icon from the address -QString RssFeed::iconUrl() const -{ - // XXX: This works for most sites but it is not perfect - return QString("http://") + QUrl(m_url).host() + QString("/favicon.ico"); -} - -// read and store the downloaded rss' informations -void RssFeed::handleFinishedDownload(const QString &url, const QString &filePath) -{ - if (url == m_url) { - qDebug() << Q_FUNC_INFO << "Successfully downloaded RSS feed at" << url; - // Parse the download RSS - m_manager->rssParser()->parseRssFile(m_url, filePath); - } else if (url == m_iconUrl) { - m_icon = filePath; - qDebug() << Q_FUNC_INFO << "icon path:" << m_icon; - m_manager->forwardFeedIconChanged(m_url, m_icon); - } -} - -void RssFeed::handleDownloadFailure(const QString &url, const QString &error) -{ - if (url != m_url) return; - - m_inErrorState = true; - m_loading = false; - m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount); - qWarning() << "Failed to download RSS feed at" << url; - qWarning() << "Reason:" << error; -} - -void RssFeed::handleFeedTitle(const QString& feedUrl, const QString& title) -{ - if (feedUrl != m_url) - return; - - if (m_title == title) - return; - - m_title = title; - - // Notify that we now have something better than a URL to display - if (m_alias.isEmpty()) - m_manager->forwardFeedInfosChanged(feedUrl, title, m_unreadCount); -} - -void RssFeed::downloadArticleTorrentIfMatching(RssDownloadRuleList* rules, const RssArticlePtr& article) -{ - Q_ASSERT(Preferences::instance()->isRssDownloadingEnabled()); - RssDownloadRulePtr matching_rule = rules->findMatchingRule(m_url, article->title()); - if (!matching_rule) - return; - - if (matching_rule->ignoreDays() > 0) { - QDateTime lastMatch = matching_rule->lastMatch(); - if (lastMatch.isValid()) { - if (QDateTime::currentDateTime() < lastMatch.addDays(matching_rule->ignoreDays())) { - connect(article.data(), SIGNAL(articleWasRead()), SLOT(handleArticleStateChanged()), Qt::UniqueConnection); - article->markAsRead(); - return; - } - } - } - matching_rule->setLastMatch(QDateTime::currentDateTime()); - rules->saveRulesToStorage(); - // Download the torrent - const QString& torrent_url = article->torrentUrl(); - if (torrent_url.isEmpty()) { - Logger::instance()->addMessage(tr("Automatic download of '%1' from '%2' RSS feed failed because it doesn't contain a torrent or a magnet link...").arg(article->title()).arg(displayName()), Log::WARNING); - article->markAsRead(); - return; - } - - Logger::instance()->addMessage(tr("Automatically downloading '%1' torrent from '%2' RSS feed...").arg(article->title()).arg(displayName())); - connect(article.data(), SIGNAL(articleWasRead()), SLOT(handleArticleStateChanged()), Qt::UniqueConnection); - if (BitTorrent::MagnetUri(torrent_url).isValid()) - article->markAsRead(); - else - connect(BitTorrent::Session::instance(), SIGNAL(downloadFromUrlFinished(QString)), article.data(), SLOT(handleTorrentDownloadSuccess(const QString&)), Qt::UniqueConnection); - - BitTorrent::AddTorrentParams params; - params.savePath = 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() -{ - Q_ASSERT(Preferences::instance()->isRssDownloadingEnabled()); - RssDownloadRuleList* rules = m_manager->downloadRules(); - foreach (const RssArticlePtr& article, m_articlesByDate) { - if (!article->isRead()) - downloadArticleTorrentIfMatching(rules, article); - } -} - -void RssFeed::handleNewArticle(const QString& feedUrl, const QVariantHash& articleData) -{ - if (feedUrl != m_url) - return; - - RssArticlePtr article = hashToRssArticle(this, articleData); - if (article.isNull()) { - qDebug() << "Article hash corrupted or guid is uncomputable; feed url: " << feedUrl; - return; - } - Q_ASSERT(article); - addArticle(article); - - m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount); - // FIXME: We should forward the information here but this would seriously decrease - // performance with current design. - //m_manager->forwardFeedContentChanged(m_url); -} - -void RssFeed::handleFeedParsingFinished(const QString& feedUrl, const QString& error) -{ - if (feedUrl != m_url) - return; - - if (!error.isEmpty()) { - qWarning() << "Failed to parse RSS feed at" << feedUrl; - qWarning() << "Reason:" << error; - } - - m_loading = false; - m_inErrorState = !error.isEmpty(); - - m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount); - // XXX: Would not be needed if we did this in handleNewArticle() instead - m_manager->forwardFeedContentChanged(m_url); - - saveItemsToDisk(); -} - -void RssFeed::handleArticleStateChanged() -{ - m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount); -} - -void RssFeed::decrementUnreadCount() -{ - --m_unreadCount; -} diff --git a/src/gui/rss/rssfeed.h b/src/gui/rss/rssfeed.h deleted file mode 100644 index 81e53a5b2..000000000 --- a/src/gui/rss/rssfeed.h +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * In addition, as a special exception, the copyright holders give permission to - * link this program with the OpenSSL project's "OpenSSL" library (or with - * modified versions of it that use the same license as the "OpenSSL" library), - * and distribute the linked executables. You must obey the GNU General Public - * License in all respects for all of the code used other than "OpenSSL". If you - * modify file(s), you may extend this exception to your version of the file(s), - * but you are not obligated to do so. If you do not wish to do so, delete this - * exception statement from your version. - * - * Contact: chris@qbittorrent.org, arnaud@qbittorrent.org - */ - -#ifndef RSSFEED_H -#define RSSFEED_H - -#include -#include -#include -#include -#include - -#include "rssfile.h" - -class RssFeed; -class RssManager; -class RssDownloadRuleList; - -typedef QHash RssArticleHash; -typedef QSharedPointer RssFeedPtr; -typedef QList RssFeedList; - -bool rssArticleDateRecentThan(const RssArticlePtr& left, const RssArticlePtr& right); - -class RssFeed: public QObject, public RssFile { - Q_OBJECT - -public: - RssFeed(RssManager* manager, RssFolder* m_parent, const QString& url); - virtual ~RssFeed(); - virtual RssFolder* parent() const { return m_parent; } - virtual void setParent(RssFolder* parent) { m_parent = parent; } - bool refresh(); - virtual QString id() const { return m_url; } - virtual void removeAllSettings(); - virtual void saveItemsToDisk(); - bool isLoading() const; - QString title() const; - virtual void rename(const QString &alias); - virtual QString displayName() const; - QString url() const; - virtual QIcon icon() const; - bool hasCustomIcon() const; - void setIconPath(const QString &pathHierarchy); - RssArticlePtr getItem(const QString &guid) const; - uint count() const; - virtual void markAsRead(); - void markAsDirty(bool dirty = true); - virtual uint unreadCount() const; - virtual RssArticleList articleListByDateDesc() const; - const RssArticleHash& articleHash() const { return m_articles; } - virtual RssArticleList unreadArticleListByDateDesc() const; - void decrementUnreadCount(); - void recheckRssItemsForDownload(); - -private slots: - void handleFinishedDownload(const QString &url, const QString &filePath); - void handleDownloadFailure(const QString &url, const QString &error); - void handleFeedTitle(const QString& feedUrl, const QString& title); - void handleNewArticle(const QString& feedUrl, const QVariantHash& article); - void handleFeedParsingFinished(const QString& feedUrl, const QString& error); - void handleArticleStateChanged(); - -private: - QString iconUrl() const; - void loadItemsFromDisk(); - void addArticle(const RssArticlePtr& article); - void downloadArticleTorrentIfMatching(RssDownloadRuleList* rules, const RssArticlePtr& article); - -private: - RssManager* m_manager; - RssArticleHash m_articles; - RssArticleList m_articlesByDate; // Articles sorted by date (more recent first) - RssFolder* m_parent; - QString m_title; - QString m_url; - QString m_alias; - QString m_icon; - QString m_iconUrl; - uint m_unreadCount; - bool m_dirty; - bool m_inErrorState; - bool m_loading; - -}; - - -#endif // RSSFEED_H diff --git a/src/gui/rss/rssfolder.cpp b/src/gui/rss/rssfolder.cpp deleted file mode 100644 index 12b16e6df..000000000 --- a/src/gui/rss/rssfolder.cpp +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * In addition, as a special exception, the copyright holders give permission to - * link this program with the OpenSSL project's "OpenSSL" library (or with - * modified versions of it that use the same license as the "OpenSSL" library), - * and distribute the linked executables. You must obey the GNU General Public - * License in all respects for all of the code used other than "OpenSSL". If you - * modify file(s), you may extend this exception to your version of the file(s), - * but you are not obligated to do so. If you do not wish to do so, delete this - * exception statement from your version. - * - * Contact: chris@qbittorrent.org, arnaud@qbittorrent.org - */ - -#include - -#include "guiiconprovider.h" -#include "rssfolder.h" -#include "rssarticle.h" -#include "base/bittorrent/session.h" -#include "rssmanager.h" -#include "rssfeed.h" - -RssFolder::RssFolder(RssFolder *parent, const QString &name): m_parent(parent), m_name(name) { -} - -RssFolder::~RssFolder() { -} - -unsigned int RssFolder::unreadCount() const { - uint nb_unread = 0; - - RssFileHash::ConstIterator it = m_children.begin(); - RssFileHash::ConstIterator itend = m_children.end(); - for ( ; it != itend; ++it) { - nb_unread += it.value()->unreadCount(); - } - return nb_unread; -} - -void RssFolder::removeChild(const QString &childId) { - if (m_children.contains(childId)) { - RssFilePtr child = m_children.take(childId); - child->removeAllSettings(); - } -} - -RssFolderPtr RssFolder::addFolder(const QString &name) { - RssFolderPtr subfolder; - if (!m_children.contains(name)) { - subfolder = RssFolderPtr(new RssFolder(this, name)); - m_children[name] = subfolder; - } else { - subfolder = qSharedPointerDynamicCast(m_children.value(name)); - } - return subfolder; -} - -RssFeedPtr RssFolder::addStream(RssManager* manager, const QString &url) { - qDebug() << Q_FUNC_INFO << manager << url; - RssFeedPtr stream(new RssFeed(manager, this, url)); - Q_ASSERT(stream); - qDebug() << "Stream URL is " << stream->url(); - Q_ASSERT(!m_children.contains(stream->url())); - m_children[stream->url()] = stream; - stream->refresh(); - return stream; -} - -// Refresh All Children -bool RssFolder::refresh() { - RssFileHash::ConstIterator it = m_children.begin(); - RssFileHash::ConstIterator itend = m_children.end(); - bool refreshed = false; - for ( ; it != itend; ++it) { - if (it.value()->refresh()) - refreshed = true; - } - return refreshed; -} - -RssArticleList RssFolder::articleListByDateDesc() const { - RssArticleList news; - - RssFileHash::ConstIterator it = m_children.begin(); - RssFileHash::ConstIterator itend = m_children.end(); - for ( ; it != itend; ++it) { - int n = news.size(); - news << it.value()->articleListByDateDesc(); - std::inplace_merge(news.begin(), news.begin() + n, news.end(), rssArticleDateRecentThan); - } - return news; -} - -RssArticleList RssFolder::unreadArticleListByDateDesc() const { - RssArticleList unread_news; - - RssFileHash::ConstIterator it = m_children.begin(); - RssFileHash::ConstIterator itend = m_children.end(); - for ( ; it != itend; ++it) { - int n = unread_news.size(); - unread_news << it.value()->unreadArticleListByDateDesc(); - std::inplace_merge(unread_news.begin(), unread_news.begin() + n, unread_news.end(), rssArticleDateRecentThan); - } - return unread_news; -} - -RssFileList RssFolder::getContent() const { - return m_children.values(); -} - -unsigned int RssFolder::getNbFeeds() const { - uint nbFeeds = 0; - - RssFileHash::ConstIterator it = m_children.begin(); - RssFileHash::ConstIterator itend = m_children.end(); - for ( ; it != itend; ++it) { - if (RssFolderPtr folder = qSharedPointerDynamicCast(it.value())) - nbFeeds += folder->getNbFeeds(); - else - ++nbFeeds; // Feed - } - return nbFeeds; -} - -QString RssFolder::displayName() const { - return m_name; -} - -void RssFolder::rename(const QString &new_name) { - if (m_name == new_name) return; - Q_ASSERT(!m_parent->hasChild(new_name)); - if (!m_parent->hasChild(new_name)) { - // Update parent - m_parent->renameChildFolder(m_name, new_name); - // Actually rename - m_name = new_name; - } -} - -void RssFolder::markAsRead() { - RssFileHash::ConstIterator it = m_children.begin(); - RssFileHash::ConstIterator itend = m_children.end(); - for ( ; it != itend; ++it) { - it.value()->markAsRead(); - } -} - -RssFeedList RssFolder::getAllFeeds() const { - RssFeedList streams; - - RssFileHash::ConstIterator it = m_children.begin(); - RssFileHash::ConstIterator itend = m_children.end(); - for ( ; it != itend; ++it) { - if (RssFeedPtr feed = qSharedPointerDynamicCast(it.value())) { - streams << feed; - } else if (RssFolderPtr folder = qSharedPointerDynamicCast(it.value())) { - streams << folder->getAllFeeds(); - } - } - return streams; -} - -QHash RssFolder::getAllFeedsAsHash() const { - QHash ret; - - RssFileHash::ConstIterator it = m_children.begin(); - RssFileHash::ConstIterator itend = m_children.end(); - for ( ; it != itend; ++it) { - if (RssFeedPtr feed = qSharedPointerDynamicCast(it.value())) { - qDebug() << Q_FUNC_INFO << feed->url(); - ret[feed->url()] = feed; - } else if (RssFolderPtr folder = qSharedPointerDynamicCast(it.value())) { - ret.unite(folder->getAllFeedsAsHash()); - } - } - return ret; -} - -void RssFolder::addFile(const RssFilePtr& item) { - if (RssFeedPtr feed = qSharedPointerDynamicCast(item)) { - Q_ASSERT(!m_children.contains(feed->url())); - m_children[feed->url()] = item; - qDebug("Added feed %s to folder ./%s", qPrintable(feed->url()), qPrintable(m_name)); - } else if (RssFolderPtr folder = qSharedPointerDynamicCast(item)) { - Q_ASSERT(!m_children.contains(folder->displayName())); - m_children[folder->displayName()] = item; - qDebug("Added folder %s to folder ./%s", qPrintable(folder->displayName()), qPrintable(m_name)); - } - // Update parent - item->setParent(this); -} - -void RssFolder::removeAllItems() { - m_children.clear(); -} - -void RssFolder::removeAllSettings() { - RssFileHash::ConstIterator it = m_children.begin(); - RssFileHash::ConstIterator itend = m_children.end(); - for ( ; it != itend; ++it) { - it.value()->removeAllSettings(); - } -} - -void RssFolder::saveItemsToDisk() -{ - foreach (const RssFilePtr& child, m_children.values()) { - child->saveItemsToDisk(); - } -} - -QString RssFolder::id() const -{ - return m_name; -} - -QIcon RssFolder::icon() const -{ - return GuiIconProvider::instance()->getIcon("inode-directory"); -} - -bool RssFolder::hasChild(const QString &childId) { - return m_children.contains(childId); -} - -void RssFolder::renameChildFolder(const QString &old_name, const QString &new_name) -{ - Q_ASSERT(m_children.contains(old_name)); - RssFilePtr folder = m_children.take(old_name); - m_children[new_name] = folder; -} - -RssFilePtr RssFolder::takeChild(const QString &childId) -{ - return m_children.take(childId); -} - -void RssFolder::recheckRssItemsForDownload() -{ - RssFileHash::ConstIterator it = m_children.begin(); - RssFileHash::ConstIterator itend = m_children.end(); - for ( ; it != itend; ++it) { - it.value()->recheckRssItemsForDownload(); - } -} diff --git a/src/gui/rss/rssfolder.h b/src/gui/rss/rssfolder.h deleted file mode 100644 index b3210fbac..000000000 --- a/src/gui/rss/rssfolder.h +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * In addition, as a special exception, the copyright holders give permission to - * link this program with the OpenSSL project's "OpenSSL" library (or with - * modified versions of it that use the same license as the "OpenSSL" library), - * and distribute the linked executables. You must obey the GNU General Public - * License in all respects for all of the code used other than "OpenSSL". If you - * modify file(s), you may extend this exception to your version of the file(s), - * but you are not obligated to do so. If you do not wish to do so, delete this - * exception statement from your version. - * - * Contact: chris@qbittorrent.org, arnaud@qbittorrent.org - */ - -#ifndef RSSFOLDER_H -#define RSSFOLDER_H - -#include -#include -#include "rssfile.h" - -class RssFolder; -class RssFeed; -class RssManager; - -typedef QHash RssFileHash; -typedef QSharedPointer RssFeedPtr; -typedef QSharedPointer RssFolderPtr; -typedef QList RssFeedList; - -class RssFolder: public QObject, public RssFile { - Q_OBJECT - -public: - RssFolder(RssFolder *parent = 0, const QString &name = QString()); - virtual ~RssFolder(); - virtual RssFolder* parent() const { return m_parent; } - virtual void setParent(RssFolder* parent) { m_parent = parent; } - virtual unsigned int unreadCount() const; - RssFeedPtr addStream(RssManager* manager, const QString &url); - RssFolderPtr addFolder(const QString &name); - unsigned int getNbFeeds() const; - RssFileList getContent() const; - RssFeedList getAllFeeds() const; - QHash getAllFeedsAsHash() const; - virtual QString displayName() const; - virtual QString id() const; - virtual QIcon icon() const; - bool hasChild(const QString &childId); - virtual RssArticleList articleListByDateDesc() const; - virtual RssArticleList unreadArticleListByDateDesc() const; - virtual void removeAllSettings(); - virtual void saveItemsToDisk(); - void removeAllItems(); - void renameChildFolder(const QString &old_name, const QString &new_name); - RssFilePtr takeChild(const QString &childId); - void recheckRssItemsForDownload(); - -public slots: - virtual bool refresh(); - void addFile(const RssFilePtr& item); - void removeChild(const QString &childId); - virtual void rename(const QString &new_name); - virtual void markAsRead(); - -private: - RssFolder *m_parent; - QString m_name; - RssFileHash m_children; -}; - -#endif // RSSFOLDER_H diff --git a/src/gui/rss/rssmanager.cpp b/src/gui/rss/rssmanager.cpp deleted file mode 100644 index 338c82d17..000000000 --- a/src/gui/rss/rssmanager.cpp +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * In addition, as a special exception, the copyright holders give permission to - * link this program with the OpenSSL project's "OpenSSL" library (or with - * modified versions of it that use the same license as the "OpenSSL" library), - * and distribute the linked executables. You must obey the GNU General Public - * License in all respects for all of the code used other than "OpenSSL". If you - * modify file(s), you may extend this exception to your version of the file(s), - * but you are not obligated to do so. If you do not wish to do so, delete this - * exception statement from your version. - * - * Contact: chris@qbittorrent.org, arnaud@qbittorrent.org - */ - -#include -#include "rssmanager.h" -#include "base/preferences.h" -#include "base/bittorrent/session.h" -#include "rssfeed.h" -#include "rssarticle.h" -#include "rssdownloadrulelist.h" -#include "rssparser.h" - -static const int MSECS_PER_MIN = 60000; - -RssManager::RssManager(): - m_downloadRules(new RssDownloadRuleList), - m_rssParser(new RssParser(this)) -{ - connect(&m_refreshTimer, SIGNAL(timeout()), SLOT(refresh())); - m_refreshInterval = Preferences::instance()->getRSSRefreshInterval(); - m_refreshTimer.start(m_refreshInterval * MSECS_PER_MIN); -} - -RssManager::~RssManager() -{ - qDebug("Deleting RSSManager..."); - delete m_downloadRules; - delete m_rssParser; - saveItemsToDisk(); - saveStreamList(); - qDebug("RSSManager deleted"); -} - -RssParser* RssManager::rssParser() const -{ - return m_rssParser; -} - -void RssManager::updateRefreshInterval(uint val) -{ - if (m_refreshInterval != val) { - m_refreshInterval = val; - m_refreshTimer.start(m_refreshInterval*60000); - qDebug("New RSS refresh interval is now every %dmin", m_refreshInterval); - } -} - -void RssManager::loadStreamList() -{ - const Preferences* const pref = Preferences::instance(); - const QStringList streamsUrl = pref->getRssFeedsUrls(); - const QStringList aliases = pref->getRssFeedsAliases(); - if (streamsUrl.size() != aliases.size()) { - std::cerr << "Corrupted Rss list, not loading it\n"; - 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"; - RssFeedPtr stream = feed_parent->addStream(this, feed_url); - const QString& alias = aliases[i]; - if (!alias.isEmpty()) { - stream->rename(alias); - } - ++i; - } - qDebug("NB RSS streams loaded: %d", streamsUrl.size()); -} - -void RssManager::forwardFeedContentChanged(const QString& url) -{ - emit feedContentChanged(url); -} - -void RssManager::forwardFeedInfosChanged(const QString& url, const QString& displayName, uint unreadCount) -{ - emit feedInfosChanged(url, displayName, unreadCount); -} - -void RssManager::forwardFeedIconChanged(const QString& url, const QString& iconPath) -{ - emit feedIconChanged(url, iconPath); -} - -void RssManager::moveFile(const RssFilePtr& file, const RssFolderPtr& destinationFolder) -{ - RssFolder* src_folder = file->parent(); - if (destinationFolder != src_folder) { - // Remove reference in old folder - src_folder->takeChild(file->id()); - // add to new Folder - destinationFolder->addFile(file); - } else { - qDebug("Nothing to move, same destination folder"); - } -} - -void RssManager::saveStreamList() const -{ - QStringList streamsUrl; - QStringList aliases; - RssFeedList streams = getAllFeeds(); - foreach (const RssFeedPtr& stream, streams) { - // This backslash has nothing to do with path handling - QString stream_path = stream->pathHierarchy().join("\\"); - if (stream_path.isNull()) - stream_path = ""; - qDebug("Saving stream path: %s", qPrintable(stream_path)); - streamsUrl << stream_path; - aliases << stream->displayName(); - } - Preferences* const pref = Preferences::instance(); - pref->setRssFeedsUrls(streamsUrl); - pref->setRssFeedsAliases(aliases); -} - -RssDownloadRuleList* RssManager::downloadRules() const -{ - Q_ASSERT(m_downloadRules); - return m_downloadRules; -} diff --git a/src/gui/rss/rssmanager.h b/src/gui/rss/rssmanager.h deleted file mode 100644 index 4a8252ccb..000000000 --- a/src/gui/rss/rssmanager.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * In addition, as a special exception, the copyright holders give permission to - * link this program with the OpenSSL project's "OpenSSL" library (or with - * modified versions of it that use the same license as the "OpenSSL" library), - * and distribute the linked executables. You must obey the GNU General Public - * License in all respects for all of the code used other than "OpenSSL". If you - * modify file(s), you may extend this exception to your version of the file(s), - * but you are not obligated to do so. If you do not wish to do so, delete this - * exception statement from your version. - * - * Contact: chris@qbittorrent.org, arnaud@qbittorrent.org - */ - -#ifndef RSSMANAGER_H -#define RSSMANAGER_H - -#include -#include - -#include "rssfolder.h" - -class RssDownloadRuleList; -class RssParser; - -class RssManager; -typedef QSharedPointer RssManagerPtr; - -class RssManager: public RssFolder { - Q_OBJECT - -public: - RssManager(); - virtual ~RssManager(); - - RssParser* rssParser() const; - RssDownloadRuleList* downloadRules() const; - -public slots: - void loadStreamList(); - void saveStreamList() const; - void forwardFeedContentChanged(const QString& url); - void forwardFeedInfosChanged(const QString& url, const QString& displayName, uint unreadCount); - void forwardFeedIconChanged(const QString& url, const QString& iconPath); - void moveFile(const RssFilePtr& file, const RssFolderPtr& destinationFolder); - void updateRefreshInterval(uint val); - -signals: - void feedContentChanged(const QString& url); - void feedInfosChanged(const QString& url, const QString& displayName, uint unreadCount); - void feedIconChanged(const QString& url, const QString& iconPath); - -private: - QTimer m_refreshTimer; - uint m_refreshInterval; - RssDownloadRuleList* m_downloadRules; - RssParser* m_rssParser; -}; - -#endif // RSSMANAGER_H diff --git a/src/gui/rss/rssparser.cpp b/src/gui/rss/rssparser.cpp deleted file mode 100644 index e09023cea..000000000 --- a/src/gui/rss/rssparser.cpp +++ /dev/null @@ -1,515 +0,0 @@ -/* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2012 Christophe Dumez - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * In addition, as a special exception, the copyright holders give permission to - * link this program with the OpenSSL project's "OpenSSL" library (or with - * modified versions of it that use the same license as the "OpenSSL" library), - * and distribute the linked executables. You must obey the GNU General Public - * License in all respects for all of the code used other than "OpenSSL". If you - * modify file(s), you may extend this exception to your version of the file(s), - * but you are not obligated to do so. If you do not wish to do so, delete this - * exception statement from your version. - * - * Contact : chris@qbittorrent.org - */ - -#include "rssparser.h" -#include "base/utils/fs.h" - -#include -#include -#include -#include -#include -#include - -struct ParsingJob { - QString feedUrl; - QString filePath; -}; - -static const char shortDay[][4] = { - "Mon", "Tue", "Wed", - "Thu", "Fri", "Sat", - "Sun" -}; -static const char longDay[][10] = { - "Monday", "Tuesday", "Wednesday", - "Thursday", "Friday", "Saturday", - "Sunday" -}; -static const char shortMonth[][4] = { - "Jan", "Feb", "Mar", "Apr", - "May", "Jun", "Jul", "Aug", - "Sep", "Oct", "Nov", "Dec" -}; - -// Ported to Qt4 from KDElibs4 -QDateTime RssParser::parseDate(const QString &string) { - const QString str = string.trimmed(); - if (str.isEmpty()) - return QDateTime::currentDateTime(); - - int nyear = 6; // indexes within string to values - int nmonth = 4; - int nday = 2; - int nwday = 1; - int nhour = 7; - int nmin = 8; - int nsec = 9; - // Also accept obsolete form "Weekday, DD-Mon-YY HH:MM:SS ±hhmm" - 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+)$"); - QStringList parts; - if (!str.indexOf(rx)) { - // Check that if date has '-' separators, both separators are '-'. - parts = rx.capturedTexts(); - bool h1 = (parts[3] == QLatin1String("-")); - bool h2 = (parts[5] == QLatin1String("-")); - if (h1 != h2) - return QDateTime::currentDateTime(); - } else { - // 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)$"); - if (str.indexOf(rx)) - return QDateTime::currentDateTime(); - nyear = 7; - nmonth = 2; - nday = 3; - nwday = 1; - nhour = 4; - nmin = 5; - nsec = 6; - parts = rx.capturedTexts(); - } - bool ok[4]; - const int day = parts[nday].toInt(&ok[0]); - int year = parts[nyear].toInt(&ok[1]); - const int hour = parts[nhour].toInt(&ok[2]); - const int minute = parts[nmin].toInt(&ok[3]); - if (!ok[0] || !ok[1] || !ok[2] || !ok[3]) - return QDateTime::currentDateTime(); - int second = 0; - if (!parts[nsec].isEmpty()) { - second = parts[nsec].toInt(&ok[0]); - if (!ok[0]) - return QDateTime::currentDateTime(); - } - bool leapSecond = (second == 60); - if (leapSecond) - second = 59; // apparently a leap second - validate below, once time zone is known - int month = 0; - for ( ; month < 12 && parts[nmonth] != shortMonth[month]; ++month) ; - int dayOfWeek = -1; - if (!parts[nwday].isEmpty()) { - // Look up the weekday name - while (++dayOfWeek < 7 && shortDay[dayOfWeek] != parts[nwday]) ; - if (dayOfWeek >= 7) - for (dayOfWeek = 0; dayOfWeek < 7 && longDay[dayOfWeek] != parts[nwday]; ++dayOfWeek) ; - } - // if (month >= 12 || dayOfWeek >= 7 - // || (dayOfWeek < 0 && format == RFCDateDay)) - // return QDateTime; - int i = parts[nyear].size(); - if (i < 4) { - // It's an obsolete year specification with less than 4 digits - year += (i == 2 && year < 50) ? 2000: 1900; - } - - // Parse the UTC offset part - int offset = 0; // set default to '-0000' - bool negOffset = false; - if (parts.count() > 10) { - rx = QRegExp("^([+-])(\\d\\d)(\\d\\d)$"); - if (!parts[10].indexOf(rx)) { - // It's a UTC offset ±hhmm - parts = rx.capturedTexts(); - offset = parts[2].toInt(&ok[0]) * 3600; - int offsetMin = parts[3].toInt(&ok[1]); - if (!ok[0] || !ok[1] || offsetMin > 59) - return QDateTime(); - offset += offsetMin * 60; - negOffset = (parts[1] == QLatin1String("-")); - if (negOffset) - offset = -offset; - } else { - // Check for an obsolete time zone name - QByteArray zone = parts[10].toLatin1(); - if (zone.length() == 1 && isalpha(zone[0]) && toupper(zone[0]) != 'J') - negOffset = true; // military zone: RFC 2822 treats as '-0000' - else if (zone != "UT" && zone != "GMT") { // treated as '+0000' - offset = (zone == "EDT") ? -4*3600 - : (zone == "EST" || zone == "CDT") ? -5*3600 - : (zone == "CST" || zone == "MDT") ? -6*3600 - : (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 - if (!qdate.isValid()) - return QDateTime::currentDateTime(); - QTime qTime(hour, minute, second); - - QDateTime result(qdate, qTime, Qt::UTC); - if (offset) - result = result.addSecs(-offset); - if (!result.isValid()) - return QDateTime::currentDateTime(); // invalid date/time - - if (leapSecond) { - // 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. - 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 result; -} - -RssParser::RssParser(QObject *parent) : - QThread(parent), m_running(true) -{ - start(); -} - -RssParser::~RssParser() -{ - m_running = false; - m_waitCondition.wakeOne(); - wait(); -} - -void RssParser::parseRssFile(const QString& feedUrl, const QString& filePath) -{ - qDebug() << Q_FUNC_INFO << feedUrl << filePath; - m_mutex.lock(); - ParsingJob job = { feedUrl, Utils::Fs::fromNativePath(filePath) }; - m_queue.enqueue(job); - // Wake up thread. - if (m_queue.count() == 1) { - qDebug() << Q_FUNC_INFO << "Waking up thread"; - m_waitCondition.wakeOne(); - } - m_mutex.unlock(); -} - -void RssParser::clearFeedData(const QString &feedUrl) -{ - m_mutex.lock(); - m_lastBuildDates.remove(feedUrl); - m_mutex.unlock(); -} - -void RssParser::run() -{ - while (m_running) { - m_mutex.lock(); - if (!m_queue.empty()) { - ParsingJob job = m_queue.dequeue(); - m_mutex.unlock(); - parseFeed(job); - } else { - qDebug() << Q_FUNC_INFO << "Thread is waiting."; - m_waitCondition.wait(&m_mutex); - qDebug() << Q_FUNC_INFO << "Thread woke up."; - m_mutex.unlock(); - } - } -} - -void RssParser::parseRssArticle(QXmlStreamReader& xml, const QString& feedUrl) -{ - QVariantHash article; - - while(!xml.atEnd()) { - xml.readNext(); - - if(xml.isEndElement() && xml.name() == "item") - break; - - if (xml.isStartElement()) { - if (xml.name() == "title") - article["title"] = xml.readElementText().trimmed(); - else if (xml.name() == "enclosure") { - 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(); - if (link.startsWith("magnet:", Qt::CaseInsensitive)) - article["torrent_url"] = link; // magnet link instead of a news URL - else - article["news_link"] = link; - } - else if (xml.name() == "description") - article["description"] = xml.readElementText().trimmed(); - else if (xml.name() == "pubDate") - article["date"] = parseDate(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")) - article["torrent_url"] = article["news_link"]; - - if (!article.contains("id")) { - // Item does not have a guid, fall back to some other identifier - const QString link = article.value("news_link").toString(); - if (!link.isEmpty()) - article["id"] = link; - else { - const QString title = article.value("title").toString(); - if (!title.isEmpty()) - article["id"] = title; - else { - qWarning() << "Item has no guid, link or title, ignoring it..."; - return; - } - } - } - - emit newArticle(feedUrl, article); -} - -void RssParser::parseRSSChannel(QXmlStreamReader& xml, const QString& feedUrl) -{ - qDebug() << Q_FUNC_INFO << feedUrl; - Q_ASSERT(xml.isStartElement() && xml.name() == "channel"); - - while(!xml.atEnd()) { - xml.readNext(); - - if (xml.isStartElement()) { - if (xml.name() == "title") { - QString title = xml.readElementText(); - emit feedTitle(feedUrl, title); - } - else if (xml.name() == "lastBuildDate") { - QString lastBuildDate = xml.readElementText(); - if (!lastBuildDate.isEmpty()) { - QMutexLocker locker(&m_mutex); - if (m_lastBuildDates.value(feedUrl, "") == lastBuildDate) { - qDebug() << "The RSS feed has not changed since last time, aborting parsing."; - return; - } - m_lastBuildDates[feedUrl] = lastBuildDate; - } - } - else if (xml.name() == "item") { - parseRssArticle(xml, feedUrl); - } - } - } -} - -void RssParser::parseAtomArticle(QXmlStreamReader& xml, const QString& feedUrl, const QString& baseUrl) -{ - QVariantHash article; - bool double_content = false; - - while(!xml.atEnd()) { - xml.readNext(); - - if(xml.isEndElement() && xml.name() == "entry") - break; - - if (xml.isStartElement()) { - if (xml.name() == "title") { - // Workaround for CDATA (QString cannot parse html escapes on it's own) - QTextDocument doc; - doc.setHtml(xml.readElementText()); - article["title"] = doc.toPlainText().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 - // 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(); - 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(); - } - } - - if (!article.contains("torrent_url") && article.contains("news_link")) - article["torrent_url"] = article["news_link"]; - - if (!article.contains("id")) { - // Item does not have a guid, fall back to some other identifier - const QString link = article.value("news_link").toString(); - if (!link.isEmpty()) - article["id"] = link; - else { - const QString title = article.value("title").toString(); - if (!title.isEmpty()) - article["id"] = title; - else { - qWarning() << "Item has no guid, link or title, ignoring it..."; - return; - } - } - } - - emit newArticle(feedUrl, article); -} - -void RssParser::parseAtomChannel(QXmlStreamReader& xml, const QString& feedUrl) -{ - qDebug() << Q_FUNC_INFO << feedUrl; - Q_ASSERT(xml.isStartElement() && xml.name() == "feed"); - - QString baseURL = xml.attributes().value("xml:base").toString(); - - while(!xml.atEnd()) { - xml.readNext(); - - if (xml.isStartElement()) { - if (xml.name() == "title") { - QString title = xml.readElementText(); - emit feedTitle(feedUrl, title); - } - else if (xml.name() == "updated") { - QString lastBuildDate = xml.readElementText(); - if (!lastBuildDate.isEmpty()) { - QMutexLocker locker(&m_mutex); - if (m_lastBuildDates.value(feedUrl) == lastBuildDate) { - qDebug() << "The RSS feed has not changed since last time, aborting parsing."; - return; - } - m_lastBuildDates[feedUrl] = lastBuildDate; - } - } - else if (xml.name() == "entry") { - parseAtomArticle(xml, feedUrl, baseURL); - } - } - } -} - -// read and create items from a rss document -void RssParser::parseFeed(const ParsingJob& job) -{ - qDebug() << Q_FUNC_INFO << job.feedUrl << job.filePath; - QFile fileRss(job.filePath); - if (!fileRss.open(QIODevice::ReadOnly | QIODevice::Text)) { - reportFailure(job, tr("Failed to open downloaded RSS file.")); - return; - } - QXmlStreamReader xml(&fileRss); - - bool found_channel = false; - while (xml.readNextStartElement()) { - if (xml.name() == "rss") { - // Find channels - while (xml.readNextStartElement()) { - if (xml.name() == "channel") { - parseRSSChannel(xml, job.feedUrl); - found_channel = true; - break; - } else { - qDebug() << "Skip rss item: " << xml.name(); - xml.skipCurrentElement(); - } - } - break; - } - else if (xml.name() == "feed") { // Atom feed - parseAtomChannel(xml, job.feedUrl); - found_channel = true; - break; - } else { - qDebug() << "Skip root item: " << xml.name(); - xml.skipCurrentElement(); - } - } - - if (xml.hasError()) { - reportFailure(job, xml.errorString()); - return; - } - - if (!found_channel) { - reportFailure(job, tr("Invalid RSS feed at '%1'.").arg(job.feedUrl)); - return; - } - - // Clean up - fileRss.close(); - emit feedParsingFinished(job.feedUrl, QString()); - Utils::Fs::forceRemove(job.filePath); -} - -void RssParser::reportFailure(const ParsingJob& job, const QString& error) -{ - emit feedParsingFinished(job.feedUrl, error); - Utils::Fs::forceRemove(job.filePath); -}