mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2026-01-01 05:08:05 -06:00
Merge pull request #4020 from glassez/rss
RSS code redesign (Issue #2433).
This commit is contained in:
@@ -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 \
|
||||
|
||||
467
src/base/rss/private/rssparser.cpp
Normal file
467
src/base/rss/private/rssparser.cpp
Normal file
@@ -0,0 +1,467 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDateTime>
|
||||
#include <QRegExp>
|
||||
#include <QStringList>
|
||||
#include <QVariant>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
69
src/base/rss/private/rssparser.h
Normal file
69
src/base/rss/private/rssparser.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef RSSPARSER_H
|
||||
#define RSSPARSER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QVariantHash>
|
||||
|
||||
class QXmlStreamReader;
|
||||
|
||||
namespace Rss
|
||||
{
|
||||
namespace Private
|
||||
{
|
||||
class Parser: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public slots:
|
||||
void parse(const QByteArray &feedData);
|
||||
|
||||
signals:
|
||||
void newArticle(const QVariantHash &rssArticle);
|
||||
void feedTitle(const QString &title);
|
||||
void finished(const QString &error);
|
||||
|
||||
private:
|
||||
void parseRssArticle(QXmlStreamReader &xml);
|
||||
void parseRSSChannel(QXmlStreamReader &xml);
|
||||
void parseAtomArticle(QXmlStreamReader &xml);
|
||||
void parseAtomChannel(QXmlStreamReader &xml);
|
||||
|
||||
QString m_lastBuildDate; // Optimization
|
||||
QString m_baseUrl;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif // RSSPARSER_H
|
||||
143
src/base/rss/rssarticle.cpp
Normal file
143
src/base/rss/rssarticle.cpp
Normal file
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <QVariant>
|
||||
#include <QDebug>
|
||||
#include <iostream>
|
||||
|
||||
#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();
|
||||
}
|
||||
91
src/base/rss/rssarticle.h
Normal file
91
src/base/rss/rssarticle.h
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef RSSARTICLE_H
|
||||
#define RSSARTICLE_H
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QVariantHash>
|
||||
#include <QSharedPointer>
|
||||
|
||||
namespace Rss
|
||||
{
|
||||
class Feed;
|
||||
class Article;
|
||||
|
||||
typedef QSharedPointer<Article> ArticlePtr;
|
||||
|
||||
// Item of a rss stream, single information
|
||||
class Article: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Article(Feed *parent, const QString &guid);
|
||||
|
||||
// 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();
|
||||
|
||||
// Serialization
|
||||
QVariantHash toHash() const;
|
||||
static ArticlePtr fromHash(Feed *parent, const QVariantHash &hash);
|
||||
|
||||
signals:
|
||||
void articleWasRead();
|
||||
|
||||
public slots:
|
||||
void handleTorrentDownloadSuccess(const QString &url);
|
||||
|
||||
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
|
||||
311
src/base/rss/rssdownloadrule.cpp
Normal file
311
src/base/rss/rssdownloadrule.cpp
Normal file
@@ -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 <QRegExp>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
|
||||
#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;
|
||||
}
|
||||
106
src/base/rss/rssdownloadrule.h
Normal file
106
src/base/rss/rssdownloadrule.h
Normal file
@@ -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 <QStringList>
|
||||
#include <QVariantHash>
|
||||
#include <QSharedPointer>
|
||||
#include <QDateTime>
|
||||
|
||||
namespace Rss
|
||||
{
|
||||
class Feed;
|
||||
typedef QSharedPointer<Feed> FeedPtr;
|
||||
|
||||
class DownloadRule;
|
||||
typedef QSharedPointer<DownloadRule> 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
|
||||
185
src/base/rss/rssdownloadrulelist.cpp
Normal file
185
src/base/rss/rssdownloadrulelist.cpp
Normal file
@@ -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 <QFile>
|
||||
#include <QDataStream>
|
||||
#include <QDebug>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
73
src/base/rss/rssdownloadrulelist.h
Normal file
73
src/base/rss/rssdownloadrulelist.h
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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 RSSDOWNLOADRULELIST_H
|
||||
#define RSSDOWNLOADRULELIST_H
|
||||
|
||||
#include <QList>
|
||||
#include <QHash>
|
||||
#include <QVariantHash>
|
||||
|
||||
#include "rssdownloadrule.h"
|
||||
|
||||
namespace Rss
|
||||
{
|
||||
class DownloadRuleList
|
||||
{
|
||||
Q_DISABLE_COPY(DownloadRuleList)
|
||||
|
||||
public:
|
||||
DownloadRuleList();
|
||||
|
||||
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:
|
||||
void loadRulesFromStorage();
|
||||
void loadRulesFromVariantHash(const QVariantHash &l);
|
||||
QVariantHash toVariantHash() const;
|
||||
|
||||
private:
|
||||
QHash<QString, DownloadRulePtr> m_rules;
|
||||
QHash<QString, QStringList> m_feedRules;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // RSSDOWNLOADFILTERLIST_H
|
||||
449
src/base/rss/rssfeed.cpp
Normal file
449
src/base/rss/rssfeed.cpp
Normal file
@@ -0,0 +1,449 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#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<QString, QVariant> allOldItems = qBTRSS.value("old_items", QHash<QString, QVariant>()).toHash();
|
||||
allOldItems[m_url] = oldItems;
|
||||
qBTRSS.setValue("old_items", allOldItems);
|
||||
}
|
||||
|
||||
void Feed::loadItemsFromDisk()
|
||||
{
|
||||
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
|
||||
QHash<QString, QVariant> allOldItems = qBTRSS.value("old_items", QHash<QString, QVariant>()).toHash();
|
||||
const QVariantList oldItems = allOldItems.value(m_url, QVariantList()).toList();
|
||||
qDebug("Loading %d old items for feed %s", oldItems.size(), qPrintable(displayName()));
|
||||
|
||||
foreach (const QVariant &var_it, oldItems) {
|
||||
QVariantHash item = var_it.toHash();
|
||||
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);
|
||||
}
|
||||
122
src/base/rss/rssfeed.h
Normal file
122
src/base/rss/rssfeed.h
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef RSSFEED_H
|
||||
#define RSSFEED_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QSharedPointer>
|
||||
#include <QVariantHash>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QNetworkCookie>
|
||||
|
||||
#include "rssfile.h"
|
||||
|
||||
namespace Rss
|
||||
{
|
||||
class Folder;
|
||||
class Feed;
|
||||
class Manager;
|
||||
class DownloadRuleList;
|
||||
|
||||
typedef QHash<QString, ArticlePtr> ArticleHash;
|
||||
typedef QSharedPointer<Feed> FeedPtr;
|
||||
typedef QList<FeedPtr> 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
|
||||
51
src/base/rss/rssfile.cpp
Normal file
51
src/base/rss/rssfile.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include "rssfolder.h"
|
||||
#include "rssfile.h"
|
||||
|
||||
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;
|
||||
}
|
||||
82
src/base/rss/rssfile.h
Normal file
82
src/base/rss/rssfile.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef RSSFILE_H
|
||||
#define RSSFILE_H
|
||||
|
||||
#include <QList>
|
||||
#include <QStringList>
|
||||
#include <QSharedPointer>
|
||||
|
||||
namespace Rss
|
||||
{
|
||||
class Folder;
|
||||
class File;
|
||||
class Article;
|
||||
|
||||
typedef QSharedPointer<File> FilePtr;
|
||||
typedef QSharedPointer<Article> ArticlePtr;
|
||||
typedef QList<ArticlePtr> ArticleList;
|
||||
typedef QList<FilePtr> FileList;
|
||||
|
||||
/**
|
||||
* Parent interface for Rss::Folder and Rss::Feed.
|
||||
*/
|
||||
class File
|
||||
{
|
||||
public:
|
||||
virtual ~File();
|
||||
|
||||
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;
|
||||
|
||||
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
|
||||
253
src/base/rss/rssfolder.cpp
Normal file
253
src/base/rss/rssfolder.cpp
Normal file
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#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<Folder>(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<Feed>(it.value()))
|
||||
streams << feed;
|
||||
else if (FolderPtr folder = qSharedPointerDynamicCast<Folder>(it.value()))
|
||||
streams << folder->getAllFeeds();
|
||||
}
|
||||
return streams;
|
||||
}
|
||||
|
||||
QHash<QString, FeedPtr> Folder::getAllFeedsAsHash() const
|
||||
{
|
||||
QHash<QString, FeedPtr> ret;
|
||||
|
||||
FileHash::ConstIterator it = m_children.begin();
|
||||
FileHash::ConstIterator itend = m_children.end();
|
||||
for ( ; it != itend; ++it) {
|
||||
if (FeedPtr feed = qSharedPointerDynamicCast<Feed>(it.value())) {
|
||||
qDebug() << Q_FUNC_INFO << feed->url();
|
||||
ret[feed->url()] = feed;
|
||||
}
|
||||
else if (FolderPtr folder = qSharedPointerDynamicCast<Folder>(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();
|
||||
}
|
||||
86
src/base/rss/rssfolder.h
Normal file
86
src/base/rss/rssfolder.h
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef RSSFOLDER_H
|
||||
#define RSSFOLDER_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QSharedPointer>
|
||||
|
||||
#include "rssfile.h"
|
||||
|
||||
namespace Rss
|
||||
{
|
||||
class Folder;
|
||||
class Feed;
|
||||
class Manager;
|
||||
|
||||
typedef QHash<QString, FilePtr> FileHash;
|
||||
typedef QSharedPointer<Feed> FeedPtr;
|
||||
typedef QSharedPointer<Folder> FolderPtr;
|
||||
typedef QList<FeedPtr> 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<QString, FeedPtr> 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
|
||||
190
src/base/rss/rssmanager.cpp
Normal file
190
src/base/rss/rssmanager.cpp
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#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<Folder>(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();
|
||||
}
|
||||
90
src/base/rss/rssmanager.h
Normal file
90
src/base/rss/rssmanager.h
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef RSSMANAGER_H
|
||||
#define RSSMANAGER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
#include <QSharedPointer>
|
||||
#include <QThread>
|
||||
|
||||
namespace Rss
|
||||
{
|
||||
class DownloadRuleList;
|
||||
class File;
|
||||
class Folder;
|
||||
class Feed;
|
||||
class Manager;
|
||||
|
||||
typedef QSharedPointer<File> FilePtr;
|
||||
typedef QSharedPointer<Folder> FolderPtr;
|
||||
typedef QSharedPointer<Feed> FeedPtr;
|
||||
|
||||
typedef QSharedPointer<Manager> 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
|
||||
Reference in New Issue
Block a user