mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-12-18 22:47:21 -06:00
Move RSS parsing to another thread to avoid freezing the UI
RSS items are now displayed as soon as they have been parsed instead of displaying all of them in one batch once the whole feed is parsed. First step to address issue #34.
This commit is contained in:
@@ -28,168 +28,12 @@
|
||||
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <QRegExp>
|
||||
#include <QVariant>
|
||||
#include <QStringList>
|
||||
#include <QDebug>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "rssarticle.h"
|
||||
|
||||
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"
|
||||
};
|
||||
static const char longMonth[][10] = {
|
||||
"January", "February", "March",
|
||||
"April", "May", "June",
|
||||
"July", "August", "September",
|
||||
"October", "November", "December"
|
||||
};
|
||||
|
||||
// Ported to Qt4 from KDElibs4
|
||||
QDateTime RssArticle::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();
|
||||
QDateTime result(qdate, QTime(hour, minute, second));
|
||||
if (!result.isValid()
|
||||
|| (dayOfWeek >= 0 && result.date().dayOfWeek() != dayOfWeek+1))
|
||||
return QDateTime::currentDateTime(); // invalid date/time, or weekday doesn't correspond with date
|
||||
if (!offset) {
|
||||
result.setTimeSpec(Qt::UTC);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
// public constructor
|
||||
RssArticle::RssArticle(RssFeed* parent, const QString &guid):
|
||||
m_parent(parent), m_guid(guid), m_read(false) {}
|
||||
@@ -211,65 +55,6 @@ QVariantHash RssArticle::toHash() const {
|
||||
return item;
|
||||
}
|
||||
|
||||
RssArticlePtr xmlToRssArticle(RssFeed* parent, QXmlStreamReader& xml)
|
||||
{
|
||||
QString guid;
|
||||
QString title;
|
||||
QString torrentUrl;
|
||||
QString link;
|
||||
QString description;
|
||||
QDateTime date;
|
||||
QString author;
|
||||
|
||||
while(!xml.atEnd()) {
|
||||
xml.readNext();
|
||||
|
||||
if(xml.isEndElement() && xml.name() == "item")
|
||||
break;
|
||||
|
||||
if (xml.isStartElement()) {
|
||||
if (xml.name() == "title")
|
||||
title = xml.readElementText();
|
||||
else if (xml.name() == "enclosure") {
|
||||
if (xml.attributes().value("type") == "application/x-bittorrent")
|
||||
torrentUrl = xml.attributes().value("url").toString();
|
||||
}
|
||||
else if (xml.name() == "link")
|
||||
link = xml.readElementText();
|
||||
else if (xml.name() == "description")
|
||||
description = xml.readElementText();
|
||||
else if (xml.name() == "pubDate")
|
||||
date = RssArticle::parseDate(xml.readElementText());
|
||||
else if (xml.name() == "author")
|
||||
author = xml.readElementText();
|
||||
else if (xml.name() == "guid")
|
||||
guid = xml.readElementText();
|
||||
}
|
||||
}
|
||||
|
||||
if (guid.isEmpty()) {
|
||||
// Item does not have a guid, fall back to some other identifier
|
||||
if (!link.isEmpty())
|
||||
guid = link;
|
||||
else if (!title.isEmpty())
|
||||
guid = title;
|
||||
else {
|
||||
qWarning() << "Item has no guid, link or title, ignoring it...";
|
||||
return RssArticlePtr();
|
||||
}
|
||||
}
|
||||
|
||||
RssArticlePtr art(new RssArticle(parent, guid));
|
||||
art->m_title = title;
|
||||
art->m_torrentUrl = torrentUrl;
|
||||
art->m_link = link;
|
||||
art->m_description = description;
|
||||
art->m_date = date;
|
||||
art->m_author = author;
|
||||
|
||||
return art;
|
||||
}
|
||||
|
||||
RssArticlePtr hashToRssArticle(RssFeed* parent, const QVariantHash &h) {
|
||||
const QString guid = h.value("id").toString();
|
||||
if (guid.isEmpty()) return RssArticlePtr();
|
||||
@@ -281,7 +66,7 @@ RssArticlePtr hashToRssArticle(RssFeed* parent, const QVariantHash &h) {
|
||||
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").toBool();
|
||||
art->m_read = h.value("read", false).toBool();
|
||||
|
||||
return art;
|
||||
}
|
||||
@@ -320,7 +105,7 @@ void RssArticle::markAsRead() {
|
||||
m_read = true;
|
||||
}
|
||||
|
||||
QString RssArticle::guid() const
|
||||
const QString& RssArticle::guid() const
|
||||
{
|
||||
return m_guid;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user