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