Christophe Dumez
14 years ago
21 changed files with 1611 additions and 1344 deletions
@ -1,716 +0,0 @@ |
|||||||
/*
|
|
||||||
* Bittorrent Client using Qt4 and libtorrent. |
|
||||||
* Copyright (C) 2006 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 "rss.h" |
|
||||||
#include "preferences.h" |
|
||||||
|
|
||||||
#if QT_VERSION < 0x040500 |
|
||||||
#include <QMap> |
|
||||||
#define QHash QMap |
|
||||||
#define toHash toMap |
|
||||||
#else |
|
||||||
#include <QHash> |
|
||||||
#endif |
|
||||||
|
|
||||||
/** RssFolder **/ |
|
||||||
|
|
||||||
RssFolder::RssFolder(RssFolder *parent, RssManager *rssmanager, Bittorrent *BTSession, QString name): parent(parent), rssmanager(rssmanager), BTSession(BTSession), name(name) { |
|
||||||
downloader = new downloadThread(this); |
|
||||||
connect(downloader, SIGNAL(downloadFinished(QString, QString)), this, SLOT(processFinishedDownload(QString, QString))); |
|
||||||
connect(downloader, SIGNAL(downloadFailure(QString, QString)), this, SLOT(handleDownloadFailure(QString, QString))); |
|
||||||
} |
|
||||||
|
|
||||||
RssFolder::~RssFolder() { |
|
||||||
qDebug("Deleting a RSS folder, removing elements"); |
|
||||||
qDeleteAll(this->values()); |
|
||||||
qDebug("Deleting downloader thread"); |
|
||||||
delete downloader; |
|
||||||
qDebug("Downloader thread removed"); |
|
||||||
} |
|
||||||
|
|
||||||
unsigned int RssFolder::getNbUnRead() const { |
|
||||||
unsigned int nb_unread = 0; |
|
||||||
foreach(RssFile *file, this->values()) { |
|
||||||
nb_unread += file->getNbUnRead(); |
|
||||||
} |
|
||||||
return nb_unread; |
|
||||||
} |
|
||||||
|
|
||||||
RssFile::FileType RssFolder::getType() const { |
|
||||||
return RssFile::FOLDER; |
|
||||||
} |
|
||||||
|
|
||||||
void RssFolder::refreshAll(){ |
|
||||||
qDebug("Refreshing all rss feeds"); |
|
||||||
const QList<RssFile*> items = this->values(); |
|
||||||
for(int i=0; i<items.size(); ++i) { |
|
||||||
//foreach(RssFile *item, *this){
|
|
||||||
RssFile *item = items.at(i); |
|
||||||
if(item->getType() == RssFile::STREAM) { |
|
||||||
RssStream* stream = (RssStream*) item; |
|
||||||
QString url = stream->getUrl(); |
|
||||||
if(stream->isLoading()) return; |
|
||||||
stream->setLoading(true); |
|
||||||
downloader->downloadUrl(url); |
|
||||||
if(!stream->hasCustomIcon()){ |
|
||||||
downloader->downloadUrl(stream->getIconUrl()); |
|
||||||
} |
|
||||||
} else { |
|
||||||
RssFolder *folder = (RssFolder*)item; |
|
||||||
folder->refreshAll(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void RssFolder::removeFile(QString ID) { |
|
||||||
if(this->contains(ID)) { |
|
||||||
RssFile* child = this->take(ID); |
|
||||||
child->removeAllSettings(); |
|
||||||
child->removeAllItems(); |
|
||||||
delete child; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
RssFolder* RssFolder::addFolder(QString name) { |
|
||||||
RssFolder *subfolder; |
|
||||||
if(!this->contains(name)) { |
|
||||||
subfolder = new RssFolder(this, rssmanager, BTSession, name); |
|
||||||
(*this)[name] = subfolder; |
|
||||||
} else { |
|
||||||
subfolder = (RssFolder*)this->value(name); |
|
||||||
} |
|
||||||
return subfolder; |
|
||||||
} |
|
||||||
|
|
||||||
RssStream* RssFolder::addStream(QString url) { |
|
||||||
RssStream* stream = new RssStream(this, rssmanager, BTSession, url); |
|
||||||
Q_ASSERT(!this->contains(stream->getUrl())); |
|
||||||
(*this)[stream->getUrl()] = stream; |
|
||||||
refreshStream(stream->getUrl()); |
|
||||||
return stream; |
|
||||||
} |
|
||||||
|
|
||||||
// Refresh All Children
|
|
||||||
void RssFolder::refresh() { |
|
||||||
foreach(RssFile *child, this->values()) { |
|
||||||
// Little optimization child->refresh() would work too
|
|
||||||
if(child->getType() == RssFile::STREAM) |
|
||||||
refreshStream(child->getID()); |
|
||||||
else |
|
||||||
child->refresh(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
QList<RssItem*> RssFolder::getNewsList() const { |
|
||||||
QList<RssItem*> news; |
|
||||||
foreach(RssFile *child, this->values()) { |
|
||||||
news << child->getNewsList(); |
|
||||||
} |
|
||||||
return news; |
|
||||||
} |
|
||||||
|
|
||||||
QList<RssItem*> RssFolder::getUnreadNewsList() const { |
|
||||||
QList<RssItem*> unread_news; |
|
||||||
foreach(RssFile *child, this->values()) { |
|
||||||
unread_news << child->getUnreadNewsList(); |
|
||||||
} |
|
||||||
return unread_news; |
|
||||||
} |
|
||||||
|
|
||||||
void RssFolder::refreshStream(QString url) { |
|
||||||
qDebug("Refreshing feed: %s", url.toLocal8Bit().data()); |
|
||||||
Q_ASSERT(this->contains(url)); |
|
||||||
RssStream *stream = (RssStream*)this->value(url); |
|
||||||
if(stream->isLoading()) { |
|
||||||
qDebug("Stream %s is already being loaded...", stream->getUrl().toLocal8Bit().data()); |
|
||||||
return; |
|
||||||
} |
|
||||||
stream->setLoading(true); |
|
||||||
qDebug("stream %s : loaded=true", stream->getUrl().toLocal8Bit().data()); |
|
||||||
downloader->downloadUrl(url); |
|
||||||
if(!stream->hasCustomIcon()){ |
|
||||||
downloader->downloadUrl(stream->getIconUrl()); |
|
||||||
}else{ |
|
||||||
qDebug("No need to download this feed's icon, it was already downloaded"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
QList<RssFile*> RssFolder::getContent() const { |
|
||||||
return this->values(); |
|
||||||
} |
|
||||||
|
|
||||||
unsigned int RssFolder::getNbFeeds() const { |
|
||||||
unsigned int nbFeeds = 0; |
|
||||||
foreach(RssFile* item, this->values()) { |
|
||||||
if(item->getType() == RssFile::FOLDER) |
|
||||||
nbFeeds += ((RssFolder*)item)->getNbFeeds(); |
|
||||||
else |
|
||||||
nbFeeds += 1; |
|
||||||
} |
|
||||||
return nbFeeds; |
|
||||||
} |
|
||||||
|
|
||||||
void RssFolder::processFinishedDownload(QString url, QString path) { |
|
||||||
if(url.endsWith("favicon.ico")){ |
|
||||||
// Icon downloaded
|
|
||||||
QImage fileIcon; |
|
||||||
if(fileIcon.load(path)) { |
|
||||||
QList<RssStream*> res = findFeedsWithIcon(url); |
|
||||||
RssStream* stream; |
|
||||||
foreach(stream, res){ |
|
||||||
stream->setIconPath(path); |
|
||||||
if(!stream->isLoading()) |
|
||||||
rssmanager->forwardFeedIconChanged(stream->getUrl(), stream->getIconPath()); |
|
||||||
} |
|
||||||
}else{ |
|
||||||
qDebug("Unsupported icon format at %s", (const char*)url.toLocal8Bit()); |
|
||||||
} |
|
||||||
return; |
|
||||||
} |
|
||||||
RssStream *stream = (RssStream*)this->value(url, 0); |
|
||||||
if(!stream){ |
|
||||||
qDebug("This rss stream was deleted in the meantime, nothing to update"); |
|
||||||
return; |
|
||||||
} |
|
||||||
stream->processDownloadedFile(path); |
|
||||||
stream->setLoading(false); |
|
||||||
qDebug("stream %s : loaded=false", stream->getUrl().toLocal8Bit().data()); |
|
||||||
// If the feed has no alias, then we use the title as Alias
|
|
||||||
// this is more user friendly
|
|
||||||
if(stream->getName().isEmpty()){ |
|
||||||
if(!stream->getTitle().isEmpty()) |
|
||||||
stream->rename(stream->getTitle()); |
|
||||||
} |
|
||||||
rssmanager->forwardFeedInfosChanged(url, stream->getName(), stream->getNbUnRead()); |
|
||||||
} |
|
||||||
|
|
||||||
void RssFolder::handleDownloadFailure(QString url, QString reason) { |
|
||||||
if(url.endsWith("favicon.ico")){ |
|
||||||
// Icon download failure
|
|
||||||
qDebug("Could not download icon at %s, reason: %s", (const char*)url.toLocal8Bit(), (const char*)reason.toLocal8Bit()); |
|
||||||
return; |
|
||||||
} |
|
||||||
RssStream *stream = (RssStream*)this->value(url, 0); |
|
||||||
if(!stream){ |
|
||||||
qDebug("This rss stream was deleted in the meantime, nothing to update"); |
|
||||||
return; |
|
||||||
} |
|
||||||
stream->setLoading(false); |
|
||||||
qDebug("Could not download Rss at %s, reason: %s", (const char*)url.toLocal8Bit(), (const char*)reason.toLocal8Bit()); |
|
||||||
stream->setDownloadFailed(); |
|
||||||
rssmanager->forwardFeedInfosChanged(url, stream->getName(), stream->getNbUnRead()); |
|
||||||
} |
|
||||||
|
|
||||||
QList<RssStream*> RssFolder::findFeedsWithIcon(QString icon_url) const { |
|
||||||
QList<RssStream*> res; |
|
||||||
RssFile* item; |
|
||||||
foreach(item, this->values()){ |
|
||||||
if(item->getType() == RssFile::STREAM && ((RssStream*)item)->getIconUrl() == icon_url) |
|
||||||
res << (RssStream*)item; |
|
||||||
} |
|
||||||
return res; |
|
||||||
} |
|
||||||
|
|
||||||
QString RssFolder::getName() const { |
|
||||||
return name; |
|
||||||
} |
|
||||||
|
|
||||||
void RssFolder::rename(QString new_name) { |
|
||||||
Q_ASSERT(!parent->contains(new_name)); |
|
||||||
if(!parent->contains(new_name)) { |
|
||||||
// Update parent
|
|
||||||
(*parent)[new_name] = parent->take(name); |
|
||||||
// Actually rename
|
|
||||||
name = new_name; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void RssFolder::markAllAsRead() { |
|
||||||
foreach(RssFile *item, this->values()) { |
|
||||||
item->markAllAsRead(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
QList<RssStream*> RssFolder::getAllFeeds() const { |
|
||||||
QList<RssStream*> streams; |
|
||||||
foreach(RssFile *item, this->values()) { |
|
||||||
if(item->getType() == RssFile::STREAM) { |
|
||||||
streams << ((RssStream*)item); |
|
||||||
} else { |
|
||||||
foreach(RssStream* stream, ((RssFolder*)item)->getAllFeeds()) { |
|
||||||
streams << stream; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return streams; |
|
||||||
} |
|
||||||
|
|
||||||
void RssFolder::addFile(RssFile * item) { |
|
||||||
if(item->getType() == RssFile::STREAM) { |
|
||||||
Q_ASSERT(!this->contains(((RssStream*)item)->getUrl())); |
|
||||||
(*this)[((RssStream*)item)->getUrl()] = item; |
|
||||||
qDebug("Added feed %s to folder ./%s", ((RssStream*)item)->getUrl().toLocal8Bit().data(), name.toLocal8Bit().data()); |
|
||||||
} else { |
|
||||||
Q_ASSERT(!this->contains(((RssFolder*)item)->getName())); |
|
||||||
(*this)[((RssFolder*)item)->getName()] = item; |
|
||||||
qDebug("Added folder %s to folder ./%s", ((RssFolder*)item)->getName().toLocal8Bit().data(), name.toLocal8Bit().data()); |
|
||||||
} |
|
||||||
// Update parent
|
|
||||||
item->setParent(this); |
|
||||||
} |
|
||||||
|
|
||||||
/** RssManager **/ |
|
||||||
|
|
||||||
RssManager::RssManager(Bittorrent *BTSession): RssFolder(0, this, BTSession, QString::null) { |
|
||||||
loadStreamList(); |
|
||||||
connect(&newsRefresher, SIGNAL(timeout()), this, SLOT(refreshAll())); |
|
||||||
refreshInterval = Preferences::getRSSRefreshInterval(); |
|
||||||
newsRefresher.start(refreshInterval*60000); |
|
||||||
} |
|
||||||
|
|
||||||
RssManager::~RssManager(){ |
|
||||||
qDebug("Deleting RSSManager"); |
|
||||||
saveStreamList(); |
|
||||||
qDebug("RSSManager deleted"); |
|
||||||
} |
|
||||||
|
|
||||||
void RssManager::updateRefreshInterval(unsigned int val){ |
|
||||||
if(refreshInterval != val) { |
|
||||||
refreshInterval = val; |
|
||||||
newsRefresher.start(refreshInterval*60000); |
|
||||||
qDebug("New RSS refresh interval is now every %dmin", refreshInterval); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void RssManager::loadStreamList(){ |
|
||||||
QIniSettings settings("qBittorrent", "qBittorrent"); |
|
||||||
QStringList streamsUrl = settings.value("Rss/streamList").toStringList(); |
|
||||||
QStringList aliases = settings.value("Rss/streamAlias").toStringList(); |
|
||||||
if(streamsUrl.size() != aliases.size()){ |
|
||||||
std::cerr << "Corrupted Rss list, not loading it\n"; |
|
||||||
return; |
|
||||||
} |
|
||||||
unsigned int i = 0; |
|
||||||
foreach(QString s, streamsUrl){ |
|
||||||
QStringList path = s.split("\\"); |
|
||||||
if(path.empty()) continue; |
|
||||||
QString feed_url = path.takeLast(); |
|
||||||
// Create feed path (if it does not exists)
|
|
||||||
RssFolder * feed_parent = this; |
|
||||||
foreach(QString folder_name, path) { |
|
||||||
feed_parent = feed_parent->addFolder(folder_name); |
|
||||||
} |
|
||||||
// Create feed
|
|
||||||
RssStream *stream = feed_parent->addStream(feed_url); |
|
||||||
QString alias = aliases.at(i); |
|
||||||
if(!alias.isEmpty()) { |
|
||||||
stream->rename(alias); |
|
||||||
} |
|
||||||
++i; |
|
||||||
} |
|
||||||
qDebug("NB RSS streams loaded: %d", streamsUrl.size()); |
|
||||||
} |
|
||||||
|
|
||||||
void RssManager::forwardFeedInfosChanged(QString url, QString aliasOrUrl, unsigned int nbUnread) { |
|
||||||
emit feedInfosChanged(url, aliasOrUrl, nbUnread); |
|
||||||
} |
|
||||||
|
|
||||||
void RssManager::forwardFeedIconChanged(QString url, QString icon_path) { |
|
||||||
emit feedIconChanged(url, icon_path); |
|
||||||
} |
|
||||||
|
|
||||||
void RssManager::moveFile(RssFile* file, RssFolder* dest_folder) { |
|
||||||
RssFolder* src_folder = file->getParent(); |
|
||||||
if(dest_folder != src_folder) { |
|
||||||
// Copy to new Folder
|
|
||||||
dest_folder->addFile(file); |
|
||||||
// Remove reference in old folder
|
|
||||||
src_folder->remove(file->getID()); |
|
||||||
} else { |
|
||||||
qDebug("Nothing to move, same destination folder"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void RssManager::saveStreamList(){ |
|
||||||
QStringList streamsUrl; |
|
||||||
QStringList aliases; |
|
||||||
const QList<RssStream*> streams = getAllFeeds(); |
|
||||||
foreach(const RssStream *stream, streams) { |
|
||||||
QString stream_path = stream->getPath().join("\\"); |
|
||||||
if(stream_path.isNull()) { |
|
||||||
stream_path = ""; |
|
||||||
} |
|
||||||
qDebug("Saving stream path: %s", qPrintable(stream_path)); |
|
||||||
streamsUrl << stream_path; |
|
||||||
aliases << stream->getName(); |
|
||||||
} |
|
||||||
QIniSettings settings("qBittorrent", "qBittorrent"); |
|
||||||
settings.beginGroup("Rss"); |
|
||||||
// FIXME: Empty folder are not saved
|
|
||||||
settings.setValue("streamList", streamsUrl); |
|
||||||
settings.setValue("streamAlias", aliases); |
|
||||||
settings.endGroup(); |
|
||||||
} |
|
||||||
|
|
||||||
/** RssStream **/ |
|
||||||
|
|
||||||
RssStream::RssStream(RssFolder* parent, RssManager *rssmanager, Bittorrent *BTSession, QString _url): parent(parent), rssmanager(rssmanager), BTSession(BTSession), alias(""), iconPath(":/Icons/rss16.png"), refreshed(false), downloadFailure(false), currently_loading(false) { |
|
||||||
qDebug("RSSStream constructed"); |
|
||||||
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss"); |
|
||||||
url = QUrl(_url).toString(); |
|
||||||
QHash<QString, QVariant> all_old_items = qBTRSS.value("old_items", QHash<QString, QVariant>()).toHash(); |
|
||||||
QVariantList old_items = all_old_items.value(url, QVariantList()).toList(); |
|
||||||
qDebug("Loading %d old items for feed %s", old_items.size(), getName().toLocal8Bit().data()); |
|
||||||
foreach(const QVariant &var_it, old_items) { |
|
||||||
QHash<QString, QVariant> item = var_it.toHash(); |
|
||||||
RssItem *rss_item = RssItem::fromHash(this, item); |
|
||||||
if(rss_item->isValid()) { |
|
||||||
(*this)[rss_item->getId()] = rss_item; |
|
||||||
} else { |
|
||||||
delete rss_item; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
RssStream::~RssStream(){ |
|
||||||
qDebug("Deleting a RSS stream: %s", getName().toLocal8Bit().data()); |
|
||||||
if(refreshed) { |
|
||||||
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss"); |
|
||||||
QVariantList old_items; |
|
||||||
foreach(RssItem *item, this->values()) { |
|
||||||
old_items << item->toHash(); |
|
||||||
} |
|
||||||
qDebug("Saving %d old items for feed %s", old_items.size(), getName().toLocal8Bit().data()); |
|
||||||
QHash<QString, QVariant> all_old_items = qBTRSS.value("old_items", QHash<QString, QVariant>()).toHash(); |
|
||||||
all_old_items[url] = old_items; |
|
||||||
qBTRSS.setValue("old_items", all_old_items); |
|
||||||
} |
|
||||||
qDebug("Removing all item from feed"); |
|
||||||
removeAllItems(); |
|
||||||
qDebug("All items were removed"); |
|
||||||
if(QFile::exists(filePath)) |
|
||||||
misc::safeRemove(filePath); |
|
||||||
if(QFile::exists(iconPath) && !iconPath.startsWith(":/")) |
|
||||||
misc::safeRemove(iconPath); |
|
||||||
} |
|
||||||
|
|
||||||
RssFile::FileType RssStream::getType() const { |
|
||||||
return RssFile::STREAM; |
|
||||||
} |
|
||||||
|
|
||||||
void RssStream::refresh() { |
|
||||||
parent->refreshStream(url); |
|
||||||
} |
|
||||||
|
|
||||||
// delete all the items saved
|
|
||||||
void RssStream::removeAllItems() { |
|
||||||
qDeleteAll(this->values()); |
|
||||||
this->clear(); |
|
||||||
} |
|
||||||
|
|
||||||
void RssStream::removeAllSettings() { |
|
||||||
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss"); |
|
||||||
QHash<QString, QVariant> feeds_w_downloader = qBTRSS.value("downloader_on", QHash<QString, QVariant>()).toHash(); |
|
||||||
if(feeds_w_downloader.contains(url)) { |
|
||||||
feeds_w_downloader.remove(url); |
|
||||||
qBTRSS.setValue("downloader_on", feeds_w_downloader); |
|
||||||
} |
|
||||||
QHash<QString, QVariant> all_feeds_filters = qBTRSS.value("feed_filters", QHash<QString, QVariant>()).toHash(); |
|
||||||
if(all_feeds_filters.contains(url)) { |
|
||||||
all_feeds_filters.remove(url); |
|
||||||
qBTRSS.setValue("feed_filters", all_feeds_filters); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
bool RssStream::itemAlreadyExists(QString name) { |
|
||||||
return this->contains(name); |
|
||||||
} |
|
||||||
|
|
||||||
void RssStream::setLoading(bool val) { |
|
||||||
currently_loading = val; |
|
||||||
} |
|
||||||
|
|
||||||
bool RssStream::isLoading() { |
|
||||||
return currently_loading; |
|
||||||
} |
|
||||||
|
|
||||||
QString RssStream::getTitle() const{ |
|
||||||
return title; |
|
||||||
} |
|
||||||
|
|
||||||
void RssStream::rename(QString new_name){ |
|
||||||
qDebug("Renaming stream to %s", new_name.toLocal8Bit().data()); |
|
||||||
alias = new_name; |
|
||||||
} |
|
||||||
|
|
||||||
// Return the alias if the stream has one, the url if it has no alias
|
|
||||||
QString RssStream::getName() const{ |
|
||||||
if(!alias.isEmpty()) { |
|
||||||
//qDebug("getName() returned alias: %s", (const char*)alias.toLocal8Bit());
|
|
||||||
return alias; |
|
||||||
} |
|
||||||
if(!title.isEmpty()) { |
|
||||||
//qDebug("getName() returned title: %s", (const char*)title.toLocal8Bit());
|
|
||||||
return title; |
|
||||||
} |
|
||||||
//qDebug("getName() returned url: %s", (const char*)url.toLocal8Bit());
|
|
||||||
return url; |
|
||||||
} |
|
||||||
|
|
||||||
QString RssStream::getLink() const{ |
|
||||||
return link; |
|
||||||
} |
|
||||||
|
|
||||||
QString RssStream::getUrl() const{ |
|
||||||
return url; |
|
||||||
} |
|
||||||
|
|
||||||
QString RssStream::getDescription() const{ |
|
||||||
return description; |
|
||||||
} |
|
||||||
|
|
||||||
QString RssStream::getImage() const{ |
|
||||||
return image; |
|
||||||
} |
|
||||||
|
|
||||||
QString RssStream::getFilePath() const{ |
|
||||||
return filePath; |
|
||||||
} |
|
||||||
|
|
||||||
QString RssStream::getIconPath() const{ |
|
||||||
if(downloadFailure) |
|
||||||
return ":/Icons/oxygen/unavailable.png"; |
|
||||||
return iconPath; |
|
||||||
} |
|
||||||
|
|
||||||
bool RssStream::hasCustomIcon() const{ |
|
||||||
return !iconPath.startsWith(":/"); |
|
||||||
} |
|
||||||
|
|
||||||
void RssStream::setIconPath(QString path) { |
|
||||||
iconPath = path; |
|
||||||
} |
|
||||||
|
|
||||||
RssItem* RssStream::getItem(QString id) const{ |
|
||||||
return this->value(id); |
|
||||||
} |
|
||||||
|
|
||||||
unsigned int RssStream::getNbNews() const{ |
|
||||||
return this->size(); |
|
||||||
} |
|
||||||
|
|
||||||
void RssStream::markAllAsRead() { |
|
||||||
foreach(RssItem *item, this->values()){ |
|
||||||
item->setRead(); |
|
||||||
} |
|
||||||
rssmanager->forwardFeedInfosChanged(url, getName(), 0); |
|
||||||
} |
|
||||||
|
|
||||||
unsigned int RssStream::getNbUnRead() const{ |
|
||||||
unsigned int nbUnread=0; |
|
||||||
foreach(RssItem *item, this->values()) { |
|
||||||
if(!item->isRead()) |
|
||||||
++nbUnread; |
|
||||||
} |
|
||||||
return nbUnread; |
|
||||||
} |
|
||||||
|
|
||||||
QList<RssItem*> RssStream::getNewsList() const{ |
|
||||||
return this->values(); |
|
||||||
} |
|
||||||
|
|
||||||
QList<RssItem*> RssStream::getUnreadNewsList() const { |
|
||||||
QList<RssItem*> unread_news; |
|
||||||
foreach(RssItem *item, this->values()) { |
|
||||||
if(!item->isRead()) |
|
||||||
unread_news << item; |
|
||||||
} |
|
||||||
return unread_news; |
|
||||||
} |
|
||||||
|
|
||||||
// download the icon from the adress
|
|
||||||
QString RssStream::getIconUrl() { |
|
||||||
QUrl siteUrl(url); |
|
||||||
return QString::fromUtf8("http://")+siteUrl.host()+QString::fromUtf8("/favicon.ico"); |
|
||||||
} |
|
||||||
|
|
||||||
// read and create items from a rss document
|
|
||||||
short RssStream::readDoc(QIODevice* device) { |
|
||||||
qDebug("Parsing RSS file..."); |
|
||||||
QXmlStreamReader xml(device); |
|
||||||
// is it a rss file ?
|
|
||||||
if (xml.atEnd()) { |
|
||||||
qDebug("ERROR: Could not parse RSS file"); |
|
||||||
return -1; |
|
||||||
} |
|
||||||
while (!xml.atEnd()) { |
|
||||||
xml.readNext(); |
|
||||||
if(xml.isStartElement()) { |
|
||||||
if(xml.name() != "rss") { |
|
||||||
qDebug("ERROR: this is not a rss file, root tag is <%s>", qPrintable(xml.name().toString())); |
|
||||||
return -1; |
|
||||||
} else { |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
// Read channels
|
|
||||||
while(!xml.atEnd()) { |
|
||||||
xml.readNext(); |
|
||||||
|
|
||||||
if(xml.isEndElement()) |
|
||||||
break; |
|
||||||
|
|
||||||
if(xml.isStartElement()) { |
|
||||||
//qDebug("xml.name() == %s", qPrintable(xml.name().toString()));
|
|
||||||
if(xml.name() == "channel") { |
|
||||||
qDebug("in channel"); |
|
||||||
|
|
||||||
// Parse channel content
|
|
||||||
while(!xml.atEnd()) { |
|
||||||
xml.readNext(); |
|
||||||
|
|
||||||
if(xml.isEndElement() && xml.name() == "channel") { |
|
||||||
break; |
|
||||||
} |
|
||||||
|
|
||||||
if(xml.isStartElement()) { |
|
||||||
//qDebug("xml.name() == %s", qPrintable(xml.name().toString()));
|
|
||||||
if(xml.name() == "title") { |
|
||||||
title = xml.readElementText(); |
|
||||||
if(alias == getUrl()) |
|
||||||
rename(title); |
|
||||||
} |
|
||||||
else if(xml.name() == "link") { |
|
||||||
link = xml.readElementText(); |
|
||||||
} |
|
||||||
else if(xml.name() == "description") { |
|
||||||
description = xml.readElementText(); |
|
||||||
} |
|
||||||
else if(xml.name() == "image") { |
|
||||||
image = xml.attributes().value("url").toString(); |
|
||||||
} |
|
||||||
else if(xml.name() == "item") { |
|
||||||
RssItem * item = new RssItem(this, xml); |
|
||||||
if(item->isValid() && !itemAlreadyExists(item->getId())) { |
|
||||||
this->insert(item->getId(), item); |
|
||||||
} else { |
|
||||||
delete item; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
resizeList(); |
|
||||||
|
|
||||||
// RSS Feed Downloader
|
|
||||||
foreach(RssItem* item, values()) { |
|
||||||
if(item->isRead()) continue; |
|
||||||
QString torrent_url; |
|
||||||
if(item->has_attachment()) |
|
||||||
torrent_url = item->getTorrentUrl(); |
|
||||||
else |
|
||||||
torrent_url = item->getLink(); |
|
||||||
// Check if the item should be automatically downloaded
|
|
||||||
FeedFilter * matching_filter = FeedFilters::getFeedFilters(url).matches(item->getTitle()); |
|
||||||
if(matching_filter != 0) { |
|
||||||
// Download the torrent
|
|
||||||
BTSession->addConsoleMessage(tr("Automatically downloading %1 torrent from %2 RSS feed...").arg(item->getTitle()).arg(getName())); |
|
||||||
if(matching_filter->isValid()) { |
|
||||||
QString save_path = matching_filter->getSavePath(); |
|
||||||
if(save_path.isEmpty()) |
|
||||||
BTSession->downloadUrlAndSkipDialog(torrent_url); |
|
||||||
else |
|
||||||
BTSession->downloadUrlAndSkipDialog(torrent_url, save_path); |
|
||||||
} else { |
|
||||||
// All torrents are downloaded from this feed
|
|
||||||
BTSession->downloadUrlAndSkipDialog(torrent_url); |
|
||||||
} |
|
||||||
// Item was downloaded, consider it as Read
|
|
||||||
item->setRead(); |
|
||||||
// Clean up
|
|
||||||
delete matching_filter; |
|
||||||
} |
|
||||||
} |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
void RssStream::resizeList() { |
|
||||||
unsigned int max_articles = Preferences::getRSSMaxArticlesPerFeed(); |
|
||||||
unsigned int nb_articles = this->size(); |
|
||||||
if(nb_articles > max_articles) { |
|
||||||
QList<RssItem*> listItem = RssManager::sortNewsList(this->values()); |
|
||||||
int excess = nb_articles - max_articles; |
|
||||||
for(int i=0; i<excess; ++i){ |
|
||||||
RssItem *lastItem = listItem.takeLast(); |
|
||||||
delete this->take(lastItem->getId()); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// existing and opening test after download
|
|
||||||
short RssStream::openRss(){ |
|
||||||
qDebug("openRss() called"); |
|
||||||
QFile fileRss(filePath); |
|
||||||
if(!fileRss.open(QIODevice::ReadOnly | QIODevice::Text)) { |
|
||||||
qDebug("openRss error: open failed, no file or locked, %s", (const char*)filePath.toLocal8Bit()); |
|
||||||
if(QFile::exists(filePath)) { |
|
||||||
fileRss.remove(); |
|
||||||
} |
|
||||||
return -1; |
|
||||||
} |
|
||||||
|
|
||||||
// start reading the xml
|
|
||||||
short return_lecture = readDoc(&fileRss); |
|
||||||
fileRss.close(); |
|
||||||
if(QFile::exists(filePath)) { |
|
||||||
fileRss.remove(); |
|
||||||
} |
|
||||||
return return_lecture; |
|
||||||
} |
|
||||||
|
|
||||||
// read and store the downloaded rss' informations
|
|
||||||
void RssStream::processDownloadedFile(QString file_path) { |
|
||||||
filePath = file_path; |
|
||||||
downloadFailure = false; |
|
||||||
if(openRss() >= 0) { |
|
||||||
refreshed = true; |
|
||||||
} else { |
|
||||||
qDebug("OpenRss: Feed update Failed"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void RssStream::setDownloadFailed(){ |
|
||||||
downloadFailure = true; |
|
||||||
} |
|
||||||
|
|
@ -1,553 +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 RSS_H |
|
||||||
#define RSS_H |
|
||||||
|
|
||||||
#include <QFile> |
|
||||||
#include <QList> |
|
||||||
#include <QTemporaryFile> |
|
||||||
#include <QXmlStreamReader> |
|
||||||
#include <QTime> |
|
||||||
#include <QUrl> |
|
||||||
#include <QTimer> |
|
||||||
#include <QImage> |
|
||||||
#include <QDateTime> |
|
||||||
#include <QTimer> |
|
||||||
#include <QUrl> |
|
||||||
|
|
||||||
#include "misc.h" |
|
||||||
#include "feeddownloader.h" |
|
||||||
#include "qbtsession.h" |
|
||||||
#include "downloadthread.h" |
|
||||||
|
|
||||||
#if QT_VERSION >= 0x040500 |
|
||||||
#include <QHash> |
|
||||||
#else |
|
||||||
#include <QMap> |
|
||||||
#define QHash QMap |
|
||||||
#define toHash toMap |
|
||||||
#endif |
|
||||||
|
|
||||||
class RssManager; |
|
||||||
class RssFile; // Folder or Stream
|
|
||||||
class RssFolder; |
|
||||||
class RssStream; |
|
||||||
class RssItem; |
|
||||||
|
|
||||||
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" |
|
||||||
}; |
|
||||||
|
|
||||||
class RssFile: public QObject { |
|
||||||
Q_OBJECT |
|
||||||
|
|
||||||
public: |
|
||||||
enum FileType {STREAM, FOLDER}; |
|
||||||
|
|
||||||
RssFile(): QObject() {} |
|
||||||
virtual ~RssFile() {} |
|
||||||
|
|
||||||
virtual unsigned int getNbUnRead() const = 0; |
|
||||||
virtual FileType getType() const = 0; |
|
||||||
virtual QString getName() const = 0; |
|
||||||
virtual QString getID() const = 0; |
|
||||||
virtual void removeAllItems() = 0; |
|
||||||
virtual void rename(QString new_name) = 0; |
|
||||||
virtual void markAllAsRead() = 0; |
|
||||||
virtual RssFolder* getParent() const = 0; |
|
||||||
virtual void setParent(RssFolder*) = 0; |
|
||||||
virtual void refresh() = 0; |
|
||||||
virtual void removeAllSettings() = 0; |
|
||||||
virtual QList<RssItem*> getNewsList() const = 0; |
|
||||||
virtual QList<RssItem*> getUnreadNewsList() const = 0; |
|
||||||
QStringList getPath() const { |
|
||||||
QStringList path; |
|
||||||
if(getParent()) { |
|
||||||
path = ((RssFile*)getParent())->getPath(); |
|
||||||
path.append(getID()); |
|
||||||
} |
|
||||||
return path; |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
// Item of a rss stream, single information
|
|
||||||
class RssItem: public QObject { |
|
||||||
Q_OBJECT |
|
||||||
private: |
|
||||||
RssStream* parent; |
|
||||||
QString id; |
|
||||||
QString title; |
|
||||||
QString torrent_url; |
|
||||||
QString news_link; |
|
||||||
QString description; |
|
||||||
QDateTime date; |
|
||||||
QString author; |
|
||||||
|
|
||||||
|
|
||||||
bool is_valid; |
|
||||||
bool read; |
|
||||||
|
|
||||||
protected: |
|
||||||
// Ported to Qt4 from KDElibs4
|
|
||||||
QDateTime parseDate(const QString &string) { |
|
||||||
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: |
|
||||||
// public constructor
|
|
||||||
RssItem(RssStream* parent, QXmlStreamReader& xml): parent(parent), read(false) { |
|
||||||
is_valid = false; |
|
||||||
torrent_url = QString::null; |
|
||||||
news_link = QString::null; |
|
||||||
title = QString::null; |
|
||||||
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") { |
|
||||||
torrent_url = xml.attributes().value("url").toString(); |
|
||||||
} |
|
||||||
} |
|
||||||
else if(xml.name() == "link") { |
|
||||||
news_link = xml.readElementText(); |
|
||||||
if(id.isEmpty()) |
|
||||||
id = news_link; |
|
||||||
} |
|
||||||
else if(xml.name() == "description") { |
|
||||||
description = xml.readElementText(); |
|
||||||
} |
|
||||||
else if(xml.name() == "pubDate") { |
|
||||||
date = parseDate(xml.readElementText()); |
|
||||||
} |
|
||||||
else if(xml.name() == "author") { |
|
||||||
author = xml.readElementText(); |
|
||||||
} |
|
||||||
else if(xml.name() == "guid") { |
|
||||||
id = xml.readElementText(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
if(!id.isEmpty()) |
|
||||||
is_valid = true; |
|
||||||
} |
|
||||||
|
|
||||||
RssItem(RssStream* parent, QString _id, QString _title, QString _torrent_url, QString _news_link, QString _description, QDateTime _date, QString _author, bool _read): |
|
||||||
parent(parent), id(_id), title(_title), torrent_url(_torrent_url), news_link(_news_link), description(_description), date(_date), author(_author), read(_read){ |
|
||||||
if(id.isEmpty()) |
|
||||||
id = news_link; |
|
||||||
if(!id.isEmpty()) { |
|
||||||
is_valid = true; |
|
||||||
} else { |
|
||||||
std::cerr << "ERROR: an invalid RSS item was saved" << std::endl; |
|
||||||
is_valid = false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
~RssItem(){ |
|
||||||
} |
|
||||||
|
|
||||||
bool has_attachment() const { |
|
||||||
return !torrent_url.isEmpty(); |
|
||||||
} |
|
||||||
|
|
||||||
QString getId() const { return id; } |
|
||||||
|
|
||||||
QHash<QString, QVariant> toHash() const { |
|
||||||
QHash<QString, QVariant> item; |
|
||||||
item["title"] = title; |
|
||||||
item["id"] = id; |
|
||||||
item["torrent_url"] = torrent_url; |
|
||||||
item["news_link"] = news_link; |
|
||||||
item["description"] = description; |
|
||||||
item["date"] = date; |
|
||||||
item["author"] = author; |
|
||||||
item["read"] = read; |
|
||||||
return item; |
|
||||||
} |
|
||||||
|
|
||||||
static RssItem* fromHash(RssStream* parent, const QHash<QString, QVariant> &h) { |
|
||||||
return new RssItem(parent, h.value("id", "").toString(), h["title"].toString(), h["torrent_url"].toString(), h["news_link"].toString(), |
|
||||||
h["description"].toString(), h["date"].toDateTime(), h["author"].toString(), h["read"].toBool()); |
|
||||||
} |
|
||||||
|
|
||||||
RssStream* getParent() const { |
|
||||||
return parent; |
|
||||||
} |
|
||||||
|
|
||||||
bool isValid() const { |
|
||||||
return is_valid; |
|
||||||
} |
|
||||||
|
|
||||||
QString getTitle() const{ |
|
||||||
return title; |
|
||||||
} |
|
||||||
|
|
||||||
QString getAuthor() const { |
|
||||||
return author; |
|
||||||
} |
|
||||||
|
|
||||||
QString getTorrentUrl() const{ |
|
||||||
return torrent_url; |
|
||||||
} |
|
||||||
|
|
||||||
QString getLink() const { |
|
||||||
return news_link; |
|
||||||
} |
|
||||||
|
|
||||||
QString getDescription() const{ |
|
||||||
if(description.isEmpty()) |
|
||||||
return tr("No description available"); |
|
||||||
return description; |
|
||||||
} |
|
||||||
|
|
||||||
QDateTime getDate() const { |
|
||||||
return date; |
|
||||||
} |
|
||||||
|
|
||||||
bool isRead() const{ |
|
||||||
return read; |
|
||||||
} |
|
||||||
|
|
||||||
void setRead(){ |
|
||||||
read = true; |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
// Rss stream, loaded form an xml file
|
|
||||||
class RssStream: public RssFile, public QHash<QString, RssItem*> { |
|
||||||
Q_OBJECT |
|
||||||
|
|
||||||
private: |
|
||||||
RssFolder *parent; |
|
||||||
RssManager *rssmanager; |
|
||||||
Bittorrent *BTSession; |
|
||||||
QString title; |
|
||||||
QString link; |
|
||||||
QString description; |
|
||||||
QString image; |
|
||||||
QString url; |
|
||||||
QString alias; |
|
||||||
QString filePath; |
|
||||||
QString iconPath; |
|
||||||
bool read; |
|
||||||
bool refreshed; |
|
||||||
bool downloadFailure; |
|
||||||
bool currently_loading; |
|
||||||
|
|
||||||
public slots: |
|
||||||
void processDownloadedFile(QString file_path); |
|
||||||
void setDownloadFailed(); |
|
||||||
|
|
||||||
public: |
|
||||||
RssStream(RssFolder* parent, RssManager *rssmanager, Bittorrent *BTSession, QString _url); |
|
||||||
~RssStream(); |
|
||||||
RssFolder* getParent() const { return parent; } |
|
||||||
void setParent(RssFolder* _parent) { parent = _parent; } |
|
||||||
FileType getType() const; |
|
||||||
void refresh(); |
|
||||||
QString getID() const { return url; } |
|
||||||
void removeAllItems(); |
|
||||||
void removeAllSettings(); |
|
||||||
bool itemAlreadyExists(QString hash); |
|
||||||
void setLoading(bool val); |
|
||||||
bool isLoading(); |
|
||||||
QString getTitle() const; |
|
||||||
void rename(QString _alias); |
|
||||||
QString getName() const; |
|
||||||
QString getLink() const; |
|
||||||
QString getUrl() const; |
|
||||||
QString getDescription() const; |
|
||||||
QString getImage() const; |
|
||||||
QString getFilePath() const; |
|
||||||
QString getIconPath() const; |
|
||||||
bool hasCustomIcon() const; |
|
||||||
void setIconPath(QString path); |
|
||||||
RssItem* getItem(QString name) const; |
|
||||||
unsigned int getNbNews() const; |
|
||||||
void markAllAsRead(); |
|
||||||
unsigned int getNbUnRead() const; |
|
||||||
QList<RssItem*> getNewsList() const; |
|
||||||
QList<RssItem*> getUnreadNewsList() const; |
|
||||||
QString getIconUrl(); |
|
||||||
|
|
||||||
private: |
|
||||||
short readDoc(QIODevice* device); |
|
||||||
void resizeList(); |
|
||||||
short openRss(); |
|
||||||
}; |
|
||||||
|
|
||||||
class RssFolder: public RssFile, public QHash<QString, RssFile*> { |
|
||||||
Q_OBJECT |
|
||||||
|
|
||||||
private: |
|
||||||
RssFolder *parent; |
|
||||||
RssManager *rssmanager; |
|
||||||
downloadThread *downloader; |
|
||||||
Bittorrent *BTSession; |
|
||||||
QString name; |
|
||||||
|
|
||||||
public: |
|
||||||
RssFolder(RssFolder *parent, RssManager *rssmanager, Bittorrent *BTSession, QString name); |
|
||||||
~RssFolder(); |
|
||||||
RssFolder* getParent() const { return parent; } |
|
||||||
void setParent(RssFolder* _parent) { parent = _parent; } |
|
||||||
unsigned int getNbUnRead() const; |
|
||||||
FileType getType() const; |
|
||||||
RssStream* addStream(QString url); |
|
||||||
RssFolder* addFolder(QString name); |
|
||||||
QList<RssStream*> findFeedsWithIcon(QString icon_url) const; |
|
||||||
unsigned int getNbFeeds() const; |
|
||||||
QList<RssFile*> getContent() const; |
|
||||||
QList<RssStream*> getAllFeeds() const; |
|
||||||
QString getName() const; |
|
||||||
QString getID() const { return name; } |
|
||||||
bool hasChild(QString ID) { return this->contains(ID); } |
|
||||||
QList<RssItem*> getNewsList() const; |
|
||||||
QList<RssItem*> getUnreadNewsList() const; |
|
||||||
void removeAllSettings() { |
|
||||||
foreach(RssFile* child, values()) { |
|
||||||
child->removeAllSettings(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void removeAllItems() { |
|
||||||
foreach(RssFile* child, values()) { |
|
||||||
child->removeAllItems(); |
|
||||||
} |
|
||||||
qDeleteAll(values()); |
|
||||||
clear(); |
|
||||||
} |
|
||||||
|
|
||||||
public slots: |
|
||||||
void refreshAll(); |
|
||||||
void addFile(RssFile * item); |
|
||||||
void removeFile(QString ID); |
|
||||||
void refresh(); |
|
||||||
void refreshStream(QString url); |
|
||||||
void processFinishedDownload(QString url, QString path); |
|
||||||
void handleDownloadFailure(QString url, QString reason); |
|
||||||
void rename(QString new_name); |
|
||||||
void markAllAsRead(); |
|
||||||
}; |
|
||||||
|
|
||||||
class RssManager: public RssFolder{ |
|
||||||
Q_OBJECT |
|
||||||
|
|
||||||
private: |
|
||||||
QTimer newsRefresher; |
|
||||||
unsigned int refreshInterval; |
|
||||||
Bittorrent *BTSession; |
|
||||||
|
|
||||||
signals: |
|
||||||
void feedInfosChanged(QString url, QString aliasOrUrl, unsigned int nbUnread); |
|
||||||
void feedIconChanged(QString url, QString icon_path); |
|
||||||
|
|
||||||
public slots: |
|
||||||
void loadStreamList(); |
|
||||||
void saveStreamList(); |
|
||||||
void forwardFeedInfosChanged(QString url, QString aliasOrUrl, unsigned int nbUnread); |
|
||||||
void forwardFeedIconChanged(QString url, QString icon_path); |
|
||||||
void moveFile(RssFile* file, RssFolder* dest_folder); |
|
||||||
void updateRefreshInterval(unsigned int val); |
|
||||||
|
|
||||||
public: |
|
||||||
RssManager(Bittorrent *BTSession); |
|
||||||
~RssManager(); |
|
||||||
static void insertSortElem(QList<RssItem*> &list, RssItem *item) { |
|
||||||
int i = 0; |
|
||||||
while(i < list.size() && item->getDate() < list.at(i)->getDate()) { |
|
||||||
++i; |
|
||||||
} |
|
||||||
list.insert(i, item); |
|
||||||
} |
|
||||||
|
|
||||||
static QList<RssItem*> sortNewsList(const QList<RssItem*>& news_list) { |
|
||||||
QList<RssItem*> new_list; |
|
||||||
foreach(RssItem *item, news_list) { |
|
||||||
insertSortElem(new_list, item); |
|
||||||
} |
|
||||||
return new_list; |
|
||||||
} |
|
||||||
|
|
||||||
}; |
|
||||||
|
|
||||||
#endif |
|
@ -0,0 +1,317 @@ |
|||||||
|
/*
|
||||||
|
* 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 <QRegExp> |
||||||
|
#include <QVariant> |
||||||
|
#include <QStringList> |
||||||
|
|
||||||
|
#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) { |
||||||
|
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, QXmlStreamReader& xml): parent(parent), read(false) { |
||||||
|
is_valid = false; |
||||||
|
torrent_url = QString::null; |
||||||
|
news_link = QString::null; |
||||||
|
title = QString::null; |
||||||
|
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") { |
||||||
|
torrent_url = xml.attributes().value("url").toString(); |
||||||
|
} |
||||||
|
} |
||||||
|
else if(xml.name() == "link") { |
||||||
|
news_link = xml.readElementText(); |
||||||
|
if(id.isEmpty()) |
||||||
|
id = news_link; |
||||||
|
} |
||||||
|
else if(xml.name() == "description") { |
||||||
|
description = xml.readElementText(); |
||||||
|
} |
||||||
|
else if(xml.name() == "pubDate") { |
||||||
|
date = parseDate(xml.readElementText()); |
||||||
|
} |
||||||
|
else if(xml.name() == "author") { |
||||||
|
author = xml.readElementText(); |
||||||
|
} |
||||||
|
else if(xml.name() == "guid") { |
||||||
|
id = xml.readElementText(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
if(!id.isEmpty()) |
||||||
|
is_valid = true; |
||||||
|
} |
||||||
|
|
||||||
|
RssArticle::RssArticle(RssFeed* parent, QString _id, QString _title, QString _torrent_url, QString _news_link, QString _description, QDateTime _date, QString _author, bool _read): |
||||||
|
parent(parent), id(_id), title(_title), torrent_url(_torrent_url), news_link(_news_link), description(_description), date(_date), author(_author), read(_read){ |
||||||
|
if(id.isEmpty()) |
||||||
|
id = news_link; |
||||||
|
if(!id.isEmpty()) { |
||||||
|
is_valid = true; |
||||||
|
} else { |
||||||
|
std::cerr << "ERROR: an invalid RSS item was saved" << std::endl; |
||||||
|
is_valid = false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
RssArticle::~RssArticle(){ |
||||||
|
} |
||||||
|
|
||||||
|
bool RssArticle::has_attachment() const { |
||||||
|
return !torrent_url.isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
QString RssArticle::getId() const { |
||||||
|
return id; |
||||||
|
} |
||||||
|
|
||||||
|
QHash<QString, QVariant> RssArticle::toHash() const { |
||||||
|
QHash<QString, QVariant> item; |
||||||
|
item["title"] = title; |
||||||
|
item["id"] = id; |
||||||
|
item["torrent_url"] = torrent_url; |
||||||
|
item["news_link"] = news_link; |
||||||
|
item["description"] = description; |
||||||
|
item["date"] = date; |
||||||
|
item["author"] = author; |
||||||
|
item["read"] = read; |
||||||
|
return item; |
||||||
|
} |
||||||
|
|
||||||
|
RssArticle* RssArticle::fromHash(RssFeed* parent, const QHash<QString, QVariant> &h) { |
||||||
|
return new RssArticle(parent, h.value("id", "").toString(), h["title"].toString(), h["torrent_url"].toString(), h["news_link"].toString(), |
||||||
|
h["description"].toString(), h["date"].toDateTime(), h["author"].toString(), h["read"].toBool()); |
||||||
|
} |
||||||
|
|
||||||
|
RssFeed* RssArticle::getParent() const { |
||||||
|
return parent; |
||||||
|
} |
||||||
|
|
||||||
|
bool RssArticle::isValid() const { |
||||||
|
return is_valid; |
||||||
|
} |
||||||
|
|
||||||
|
QString RssArticle::getTitle() const{ |
||||||
|
return title; |
||||||
|
} |
||||||
|
|
||||||
|
QString RssArticle::getAuthor() const { |
||||||
|
return author; |
||||||
|
} |
||||||
|
|
||||||
|
QString RssArticle::getTorrentUrl() const{ |
||||||
|
return torrent_url; |
||||||
|
} |
||||||
|
|
||||||
|
QString RssArticle::getLink() const { |
||||||
|
return news_link; |
||||||
|
} |
||||||
|
|
||||||
|
QString RssArticle::getDescription() const{ |
||||||
|
if(description.isEmpty()) |
||||||
|
return tr("No description available"); |
||||||
|
return description; |
||||||
|
} |
||||||
|
|
||||||
|
QDateTime RssArticle::getDate() const { |
||||||
|
return date; |
||||||
|
} |
||||||
|
|
||||||
|
bool RssArticle::isRead() const{ |
||||||
|
return read; |
||||||
|
} |
||||||
|
|
||||||
|
void RssArticle::setRead(){ |
||||||
|
read = true; |
||||||
|
} |
@ -0,0 +1,82 @@ |
|||||||
|
/*
|
||||||
|
* 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 RSSARTICLE_H |
||||||
|
#define RSSARTICLE_H |
||||||
|
|
||||||
|
#include <QXmlStreamReader> |
||||||
|
#include <QDateTime> |
||||||
|
#include <QHash> |
||||||
|
|
||||||
|
class RssFeed; |
||||||
|
|
||||||
|
// Item of a rss stream, single information
|
||||||
|
class RssArticle: public QObject { |
||||||
|
Q_OBJECT |
||||||
|
|
||||||
|
public: |
||||||
|
RssArticle(RssFeed* parent, QXmlStreamReader& xml); |
||||||
|
RssArticle(RssFeed* parent, QString _id, QString _title, |
||||||
|
QString _torrent_url, QString _news_link, QString _description, |
||||||
|
QDateTime _date, QString _author, bool _read); |
||||||
|
~RssArticle(); |
||||||
|
bool has_attachment() const; |
||||||
|
QString getId() const; |
||||||
|
QHash<QString, QVariant> toHash() const; |
||||||
|
static RssArticle* fromHash(RssFeed* parent, const QHash<QString, QVariant> &h); |
||||||
|
RssFeed* getParent() const; |
||||||
|
bool isValid() const; |
||||||
|
QString getTitle() const; |
||||||
|
QString getAuthor() const; |
||||||
|
QString getTorrentUrl() const; |
||||||
|
QString getLink() const; |
||||||
|
QString getDescription() const; |
||||||
|
QDateTime getDate() const; |
||||||
|
bool isRead() const; |
||||||
|
void setRead(); |
||||||
|
|
||||||
|
protected: |
||||||
|
QDateTime parseDate(const QString &string); |
||||||
|
|
||||||
|
private: |
||||||
|
RssFeed* parent; |
||||||
|
QString id; |
||||||
|
QString title; |
||||||
|
QString torrent_url; |
||||||
|
QString news_link; |
||||||
|
QString description; |
||||||
|
QDateTime date; |
||||||
|
QString author; |
||||||
|
bool is_valid; |
||||||
|
bool read; |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
#endif // RSSARTICLE_H
|
@ -0,0 +1,370 @@ |
|||||||
|
/*
|
||||||
|
* 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 "rssfeed.h" |
||||||
|
#include "rssmanager.h" |
||||||
|
#include "qbtsession.h" |
||||||
|
#include "rssfolder.h" |
||||||
|
#include "preferences.h" |
||||||
|
#include "rssarticle.h" |
||||||
|
#include "feeddownloader.h" |
||||||
|
|
||||||
|
RssFeed::RssFeed(RssFolder* parent, RssManager *rssmanager, Bittorrent *BTSession, QString _url): parent(parent), rssmanager(rssmanager), BTSession(BTSession), alias(""), iconPath(":/Icons/rss16.png"), refreshed(false), downloadFailure(false), currently_loading(false) { |
||||||
|
qDebug("RSSStream constructed"); |
||||||
|
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss"); |
||||||
|
url = QUrl(_url).toString(); |
||||||
|
QHash<QString, QVariant> all_old_items = qBTRSS.value("old_items", QHash<QString, QVariant>()).toHash(); |
||||||
|
QVariantList old_items = all_old_items.value(url, QVariantList()).toList(); |
||||||
|
qDebug("Loading %d old items for feed %s", old_items.size(), getName().toLocal8Bit().data()); |
||||||
|
foreach(const QVariant &var_it, old_items) { |
||||||
|
QHash<QString, QVariant> item = var_it.toHash(); |
||||||
|
RssArticle *rss_item = RssArticle::fromHash(this, item); |
||||||
|
if(rss_item->isValid()) { |
||||||
|
(*this)[rss_item->getId()] = rss_item; |
||||||
|
} else { |
||||||
|
delete rss_item; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
RssFeed::~RssFeed(){ |
||||||
|
qDebug("Deleting a RSS stream: %s", getName().toLocal8Bit().data()); |
||||||
|
if(refreshed) { |
||||||
|
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss"); |
||||||
|
QVariantList old_items; |
||||||
|
foreach(RssArticle *item, this->values()) { |
||||||
|
old_items << item->toHash(); |
||||||
|
} |
||||||
|
qDebug("Saving %d old items for feed %s", old_items.size(), getName().toLocal8Bit().data()); |
||||||
|
QHash<QString, QVariant> all_old_items = qBTRSS.value("old_items", QHash<QString, QVariant>()).toHash(); |
||||||
|
all_old_items[url] = old_items; |
||||||
|
qBTRSS.setValue("old_items", all_old_items); |
||||||
|
} |
||||||
|
qDebug("Removing all item from feed"); |
||||||
|
removeAllItems(); |
||||||
|
qDebug("All items were removed"); |
||||||
|
if(QFile::exists(filePath)) |
||||||
|
misc::safeRemove(filePath); |
||||||
|
if(QFile::exists(iconPath) && !iconPath.startsWith(":/")) |
||||||
|
misc::safeRemove(iconPath); |
||||||
|
} |
||||||
|
|
||||||
|
RssFile::FileType RssFeed::getType() const { |
||||||
|
return RssFile::FEED; |
||||||
|
} |
||||||
|
|
||||||
|
void RssFeed::refresh() { |
||||||
|
parent->refreshStream(url); |
||||||
|
} |
||||||
|
|
||||||
|
// delete all the items saved
|
||||||
|
void RssFeed::removeAllItems() { |
||||||
|
qDeleteAll(this->values()); |
||||||
|
this->clear(); |
||||||
|
} |
||||||
|
|
||||||
|
void RssFeed::removeAllSettings() { |
||||||
|
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss"); |
||||||
|
QHash<QString, QVariant> feeds_w_downloader = qBTRSS.value("downloader_on", QHash<QString, QVariant>()).toHash(); |
||||||
|
if(feeds_w_downloader.contains(url)) { |
||||||
|
feeds_w_downloader.remove(url); |
||||||
|
qBTRSS.setValue("downloader_on", feeds_w_downloader); |
||||||
|
} |
||||||
|
QHash<QString, QVariant> all_feeds_filters = qBTRSS.value("feed_filters", QHash<QString, QVariant>()).toHash(); |
||||||
|
if(all_feeds_filters.contains(url)) { |
||||||
|
all_feeds_filters.remove(url); |
||||||
|
qBTRSS.setValue("feed_filters", all_feeds_filters); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
bool RssFeed::itemAlreadyExists(QString name) { |
||||||
|
return this->contains(name); |
||||||
|
} |
||||||
|
|
||||||
|
void RssFeed::setLoading(bool val) { |
||||||
|
currently_loading = val; |
||||||
|
} |
||||||
|
|
||||||
|
bool RssFeed::isLoading() { |
||||||
|
return currently_loading; |
||||||
|
} |
||||||
|
|
||||||
|
QString RssFeed::getTitle() const{ |
||||||
|
return title; |
||||||
|
} |
||||||
|
|
||||||
|
void RssFeed::rename(QString new_name){ |
||||||
|
qDebug("Renaming stream to %s", new_name.toLocal8Bit().data()); |
||||||
|
alias = new_name; |
||||||
|
} |
||||||
|
|
||||||
|
// Return the alias if the stream has one, the url if it has no alias
|
||||||
|
QString RssFeed::getName() const{ |
||||||
|
if(!alias.isEmpty()) { |
||||||
|
//qDebug("getName() returned alias: %s", (const char*)alias.toLocal8Bit());
|
||||||
|
return alias; |
||||||
|
} |
||||||
|
if(!title.isEmpty()) { |
||||||
|
//qDebug("getName() returned title: %s", (const char*)title.toLocal8Bit());
|
||||||
|
return title; |
||||||
|
} |
||||||
|
//qDebug("getName() returned url: %s", (const char*)url.toLocal8Bit());
|
||||||
|
return url; |
||||||
|
} |
||||||
|
|
||||||
|
QString RssFeed::getLink() const{ |
||||||
|
return link; |
||||||
|
} |
||||||
|
|
||||||
|
QString RssFeed::getUrl() const{ |
||||||
|
return url; |
||||||
|
} |
||||||
|
|
||||||
|
QString RssFeed::getDescription() const{ |
||||||
|
return description; |
||||||
|
} |
||||||
|
|
||||||
|
QString RssFeed::getImage() const{ |
||||||
|
return image; |
||||||
|
} |
||||||
|
|
||||||
|
QString RssFeed::getFilePath() const{ |
||||||
|
return filePath; |
||||||
|
} |
||||||
|
|
||||||
|
QString RssFeed::getIconPath() const{ |
||||||
|
if(downloadFailure) |
||||||
|
return ":/Icons/oxygen/unavailable.png"; |
||||||
|
return iconPath; |
||||||
|
} |
||||||
|
|
||||||
|
bool RssFeed::hasCustomIcon() const{ |
||||||
|
return !iconPath.startsWith(":/"); |
||||||
|
} |
||||||
|
|
||||||
|
void RssFeed::setIconPath(QString path) { |
||||||
|
iconPath = path; |
||||||
|
} |
||||||
|
|
||||||
|
RssArticle* RssFeed::getItem(QString id) const{ |
||||||
|
return this->value(id); |
||||||
|
} |
||||||
|
|
||||||
|
unsigned int RssFeed::getNbNews() const{ |
||||||
|
return this->size(); |
||||||
|
} |
||||||
|
|
||||||
|
void RssFeed::markAllAsRead() { |
||||||
|
foreach(RssArticle *item, this->values()){ |
||||||
|
item->setRead(); |
||||||
|
} |
||||||
|
rssmanager->forwardFeedInfosChanged(url, getName(), 0); |
||||||
|
} |
||||||
|
|
||||||
|
unsigned int RssFeed::getNbUnRead() const{ |
||||||
|
unsigned int nbUnread=0; |
||||||
|
foreach(RssArticle *item, this->values()) { |
||||||
|
if(!item->isRead()) |
||||||
|
++nbUnread; |
||||||
|
} |
||||||
|
return nbUnread; |
||||||
|
} |
||||||
|
|
||||||
|
QList<RssArticle*> RssFeed::getNewsList() const{ |
||||||
|
return this->values(); |
||||||
|
} |
||||||
|
|
||||||
|
QList<RssArticle*> RssFeed::getUnreadNewsList() const { |
||||||
|
QList<RssArticle*> unread_news; |
||||||
|
foreach(RssArticle *item, this->values()) { |
||||||
|
if(!item->isRead()) |
||||||
|
unread_news << item; |
||||||
|
} |
||||||
|
return unread_news; |
||||||
|
} |
||||||
|
|
||||||
|
// download the icon from the adress
|
||||||
|
QString RssFeed::getIconUrl() { |
||||||
|
QUrl siteUrl(url); |
||||||
|
return QString::fromUtf8("http://")+siteUrl.host()+QString::fromUtf8("/favicon.ico"); |
||||||
|
} |
||||||
|
|
||||||
|
// read and create items from a rss document
|
||||||
|
short RssFeed::readDoc(QIODevice* device) { |
||||||
|
qDebug("Parsing RSS file..."); |
||||||
|
QXmlStreamReader xml(device); |
||||||
|
// is it a rss file ?
|
||||||
|
if (xml.atEnd()) { |
||||||
|
qDebug("ERROR: Could not parse RSS file"); |
||||||
|
return -1; |
||||||
|
} |
||||||
|
while (!xml.atEnd()) { |
||||||
|
xml.readNext(); |
||||||
|
if(xml.isStartElement()) { |
||||||
|
if(xml.name() != "rss") { |
||||||
|
qDebug("ERROR: this is not a rss file, root tag is <%s>", qPrintable(xml.name().toString())); |
||||||
|
return -1; |
||||||
|
} else { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
// Read channels
|
||||||
|
while(!xml.atEnd()) { |
||||||
|
xml.readNext(); |
||||||
|
|
||||||
|
if(xml.isEndElement()) |
||||||
|
break; |
||||||
|
|
||||||
|
if(xml.isStartElement()) { |
||||||
|
//qDebug("xml.name() == %s", qPrintable(xml.name().toString()));
|
||||||
|
if(xml.name() == "channel") { |
||||||
|
qDebug("in channel"); |
||||||
|
|
||||||
|
// Parse channel content
|
||||||
|
while(!xml.atEnd()) { |
||||||
|
xml.readNext(); |
||||||
|
|
||||||
|
if(xml.isEndElement() && xml.name() == "channel") { |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
if(xml.isStartElement()) { |
||||||
|
//qDebug("xml.name() == %s", qPrintable(xml.name().toString()));
|
||||||
|
if(xml.name() == "title") { |
||||||
|
title = xml.readElementText(); |
||||||
|
if(alias == getUrl()) |
||||||
|
rename(title); |
||||||
|
} |
||||||
|
else if(xml.name() == "link") { |
||||||
|
link = xml.readElementText(); |
||||||
|
} |
||||||
|
else if(xml.name() == "description") { |
||||||
|
description = xml.readElementText(); |
||||||
|
} |
||||||
|
else if(xml.name() == "image") { |
||||||
|
image = xml.attributes().value("url").toString(); |
||||||
|
} |
||||||
|
else if(xml.name() == "item") { |
||||||
|
RssArticle * item = new RssArticle(this, xml); |
||||||
|
if(item->isValid() && !itemAlreadyExists(item->getId())) { |
||||||
|
this->insert(item->getId(), item); |
||||||
|
} else { |
||||||
|
delete item; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
resizeList(); |
||||||
|
|
||||||
|
// RSS Feed Downloader
|
||||||
|
foreach(RssArticle* item, values()) { |
||||||
|
if(item->isRead()) continue; |
||||||
|
QString torrent_url; |
||||||
|
if(item->has_attachment()) |
||||||
|
torrent_url = item->getTorrentUrl(); |
||||||
|
else |
||||||
|
torrent_url = item->getLink(); |
||||||
|
// Check if the item should be automatically downloaded
|
||||||
|
FeedFilter * matching_filter = FeedFilters::getFeedFilters(url).matches(item->getTitle()); |
||||||
|
if(matching_filter != 0) { |
||||||
|
// Download the torrent
|
||||||
|
BTSession->addConsoleMessage(tr("Automatically downloading %1 torrent from %2 RSS feed...").arg(item->getTitle()).arg(getName())); |
||||||
|
if(matching_filter->isValid()) { |
||||||
|
QString save_path = matching_filter->getSavePath(); |
||||||
|
if(save_path.isEmpty()) |
||||||
|
BTSession->downloadUrlAndSkipDialog(torrent_url); |
||||||
|
else |
||||||
|
BTSession->downloadUrlAndSkipDialog(torrent_url, save_path); |
||||||
|
} else { |
||||||
|
// All torrents are downloaded from this feed
|
||||||
|
BTSession->downloadUrlAndSkipDialog(torrent_url); |
||||||
|
} |
||||||
|
// Item was downloaded, consider it as Read
|
||||||
|
item->setRead(); |
||||||
|
// Clean up
|
||||||
|
delete matching_filter; |
||||||
|
} |
||||||
|
} |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
void RssFeed::resizeList() { |
||||||
|
unsigned int max_articles = Preferences::getRSSMaxArticlesPerFeed(); |
||||||
|
unsigned int nb_articles = this->size(); |
||||||
|
if(nb_articles > max_articles) { |
||||||
|
QList<RssArticle*> listItem = RssManager::sortNewsList(this->values()); |
||||||
|
int excess = nb_articles - max_articles; |
||||||
|
for(int i=0; i<excess; ++i){ |
||||||
|
RssArticle *lastItem = listItem.takeLast(); |
||||||
|
delete this->take(lastItem->getId()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// existing and opening test after download
|
||||||
|
short RssFeed::openRss(){ |
||||||
|
qDebug("openRss() called"); |
||||||
|
QFile fileRss(filePath); |
||||||
|
if(!fileRss.open(QIODevice::ReadOnly | QIODevice::Text)) { |
||||||
|
qDebug("openRss error: open failed, no file or locked, %s", (const char*)filePath.toLocal8Bit()); |
||||||
|
if(QFile::exists(filePath)) { |
||||||
|
fileRss.remove(); |
||||||
|
} |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// start reading the xml
|
||||||
|
short return_lecture = readDoc(&fileRss); |
||||||
|
fileRss.close(); |
||||||
|
if(QFile::exists(filePath)) { |
||||||
|
fileRss.remove(); |
||||||
|
} |
||||||
|
return return_lecture; |
||||||
|
} |
||||||
|
|
||||||
|
// read and store the downloaded rss' informations
|
||||||
|
void RssFeed::processDownloadedFile(QString file_path) { |
||||||
|
filePath = file_path; |
||||||
|
downloadFailure = false; |
||||||
|
if(openRss() >= 0) { |
||||||
|
refreshed = true; |
||||||
|
} else { |
||||||
|
qDebug("OpenRss: Feed update Failed"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void RssFeed::setDownloadFailed(){ |
||||||
|
downloadFailure = true; |
||||||
|
} |
@ -0,0 +1,104 @@ |
|||||||
|
/*
|
||||||
|
* 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 "rssfile.h" |
||||||
|
|
||||||
|
class RssManager; |
||||||
|
class Bittorrent; |
||||||
|
|
||||||
|
class RssFeed: public RssFile, public QHash<QString, RssArticle*> { |
||||||
|
Q_OBJECT |
||||||
|
|
||||||
|
private: |
||||||
|
RssFolder *parent; |
||||||
|
RssManager *rssmanager; |
||||||
|
Bittorrent *BTSession; |
||||||
|
QString title; |
||||||
|
QString link; |
||||||
|
QString description; |
||||||
|
QString image; |
||||||
|
QString url; |
||||||
|
QString alias; |
||||||
|
QString filePath; |
||||||
|
QString iconPath; |
||||||
|
bool read; |
||||||
|
bool refreshed; |
||||||
|
bool downloadFailure; |
||||||
|
bool currently_loading; |
||||||
|
|
||||||
|
public slots: |
||||||
|
void processDownloadedFile(QString file_path); |
||||||
|
void setDownloadFailed(); |
||||||
|
|
||||||
|
public: |
||||||
|
RssFeed(RssFolder* parent, RssManager *rssmanager, Bittorrent *BTSession, QString _url); |
||||||
|
~RssFeed(); |
||||||
|
RssFolder* getParent() const { return parent; } |
||||||
|
void setParent(RssFolder* _parent) { parent = _parent; } |
||||||
|
FileType getType() const; |
||||||
|
void refresh(); |
||||||
|
QString getID() const { return url; } |
||||||
|
void removeAllItems(); |
||||||
|
void removeAllSettings(); |
||||||
|
bool itemAlreadyExists(QString hash); |
||||||
|
void setLoading(bool val); |
||||||
|
bool isLoading(); |
||||||
|
QString getTitle() const; |
||||||
|
void rename(QString _alias); |
||||||
|
QString getName() const; |
||||||
|
QString getLink() const; |
||||||
|
QString getUrl() const; |
||||||
|
QString getDescription() const; |
||||||
|
QString getImage() const; |
||||||
|
QString getFilePath() const; |
||||||
|
QString getIconPath() const; |
||||||
|
bool hasCustomIcon() const; |
||||||
|
void setIconPath(QString path); |
||||||
|
RssArticle* getItem(QString name) const; |
||||||
|
unsigned int getNbNews() const; |
||||||
|
void markAllAsRead(); |
||||||
|
unsigned int getNbUnRead() const; |
||||||
|
QList<RssArticle*> getNewsList() const; |
||||||
|
QList<RssArticle*> getUnreadNewsList() const; |
||||||
|
QString getIconUrl(); |
||||||
|
|
||||||
|
private: |
||||||
|
short readDoc(QIODevice* device); |
||||||
|
void resizeList(); |
||||||
|
short openRss(); |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
#endif // RSSFEED_H
|
@ -0,0 +1,72 @@ |
|||||||
|
/*
|
||||||
|
* 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 RSSFILE_H |
||||||
|
#define RSSFILE_H |
||||||
|
|
||||||
|
#include <QObject> |
||||||
|
#include <QStringList> |
||||||
|
|
||||||
|
class RssArticle; |
||||||
|
class RssFolder; |
||||||
|
|
||||||
|
class RssFile: public QObject { |
||||||
|
Q_OBJECT |
||||||
|
|
||||||
|
public: |
||||||
|
enum FileType {FEED, FOLDER}; |
||||||
|
|
||||||
|
RssFile(): QObject() {} |
||||||
|
virtual ~RssFile() {} |
||||||
|
|
||||||
|
virtual unsigned int getNbUnRead() const = 0; |
||||||
|
virtual FileType getType() const = 0; |
||||||
|
virtual QString getName() const = 0; |
||||||
|
virtual QString getID() const = 0; |
||||||
|
virtual void removeAllItems() = 0; |
||||||
|
virtual void rename(QString new_name) = 0; |
||||||
|
virtual void markAllAsRead() = 0; |
||||||
|
virtual RssFolder* getParent() const = 0; |
||||||
|
virtual void setParent(RssFolder*) = 0; |
||||||
|
virtual void refresh() = 0; |
||||||
|
virtual void removeAllSettings() = 0; |
||||||
|
virtual QList<RssArticle*> getNewsList() const = 0; |
||||||
|
virtual QList<RssArticle*> getUnreadNewsList() const = 0; |
||||||
|
QStringList getPath() const { |
||||||
|
QStringList path; |
||||||
|
if(getParent()) { |
||||||
|
path = ((RssFile*)getParent())->getPath(); |
||||||
|
path.append(getID()); |
||||||
|
} |
||||||
|
return path; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
#endif // RSSFILE_H
|
@ -0,0 +1,303 @@ |
|||||||
|
/*
|
||||||
|
* 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 "rssfolder.h" |
||||||
|
#include "rssarticle.h" |
||||||
|
#include "qbtsession.h" |
||||||
|
#include "downloadthread.h" |
||||||
|
#include "rssmanager.h" |
||||||
|
#include "rssfeed.h" |
||||||
|
|
||||||
|
RssFolder::RssFolder(RssFolder *parent, RssManager *rssmanager, Bittorrent *BTSession, QString name): parent(parent), rssmanager(rssmanager), BTSession(BTSession), name(name) { |
||||||
|
downloader = new downloadThread(this); |
||||||
|
connect(downloader, SIGNAL(downloadFinished(QString, QString)), this, SLOT(processFinishedDownload(QString, QString))); |
||||||
|
connect(downloader, SIGNAL(downloadFailure(QString, QString)), this, SLOT(handleDownloadFailure(QString, QString))); |
||||||
|
} |
||||||
|
|
||||||
|
RssFolder::~RssFolder() { |
||||||
|
qDebug("Deleting a RSS folder, removing elements"); |
||||||
|
qDeleteAll(this->values()); |
||||||
|
qDebug("Deleting downloader thread"); |
||||||
|
delete downloader; |
||||||
|
qDebug("Downloader thread removed"); |
||||||
|
} |
||||||
|
|
||||||
|
unsigned int RssFolder::getNbUnRead() const { |
||||||
|
unsigned int nb_unread = 0; |
||||||
|
foreach(RssFile *file, this->values()) { |
||||||
|
nb_unread += file->getNbUnRead(); |
||||||
|
} |
||||||
|
return nb_unread; |
||||||
|
} |
||||||
|
|
||||||
|
RssFile::FileType RssFolder::getType() const { |
||||||
|
return RssFile::FOLDER; |
||||||
|
} |
||||||
|
|
||||||
|
void RssFolder::refreshAll(){ |
||||||
|
qDebug("Refreshing all rss feeds"); |
||||||
|
const QList<RssFile*> items = this->values(); |
||||||
|
for(int i=0; i<items.size(); ++i) { |
||||||
|
//foreach(RssFile *item, *this){
|
||||||
|
RssFile *item = items.at(i); |
||||||
|
if(item->getType() == RssFile::FEED) { |
||||||
|
RssFeed* stream = (RssFeed*) item; |
||||||
|
QString url = stream->getUrl(); |
||||||
|
if(stream->isLoading()) return; |
||||||
|
stream->setLoading(true); |
||||||
|
downloader->downloadUrl(url); |
||||||
|
if(!stream->hasCustomIcon()){ |
||||||
|
downloader->downloadUrl(stream->getIconUrl()); |
||||||
|
} |
||||||
|
} else { |
||||||
|
RssFolder *folder = (RssFolder*)item; |
||||||
|
folder->refreshAll(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void RssFolder::removeFile(QString ID) { |
||||||
|
if(this->contains(ID)) { |
||||||
|
RssFile* child = this->take(ID); |
||||||
|
child->removeAllSettings(); |
||||||
|
child->removeAllItems(); |
||||||
|
delete child; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
RssFolder* RssFolder::addFolder(QString name) { |
||||||
|
RssFolder *subfolder; |
||||||
|
if(!this->contains(name)) { |
||||||
|
subfolder = new RssFolder(this, rssmanager, BTSession, name); |
||||||
|
(*this)[name] = subfolder; |
||||||
|
} else { |
||||||
|
subfolder = (RssFolder*)this->value(name); |
||||||
|
} |
||||||
|
return subfolder; |
||||||
|
} |
||||||
|
|
||||||
|
RssFeed* RssFolder::addStream(QString url) { |
||||||
|
RssFeed* stream = new RssFeed(this, rssmanager, BTSession, url); |
||||||
|
Q_ASSERT(!this->contains(stream->getUrl())); |
||||||
|
(*this)[stream->getUrl()] = stream; |
||||||
|
refreshStream(stream->getUrl()); |
||||||
|
return stream; |
||||||
|
} |
||||||
|
|
||||||
|
// Refresh All Children
|
||||||
|
void RssFolder::refresh() { |
||||||
|
foreach(RssFile *child, this->values()) { |
||||||
|
// Little optimization child->refresh() would work too
|
||||||
|
if(child->getType() == RssFile::FEED) |
||||||
|
refreshStream(child->getID()); |
||||||
|
else |
||||||
|
child->refresh(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
QList<RssArticle*> RssFolder::getNewsList() const { |
||||||
|
QList<RssArticle*> news; |
||||||
|
foreach(RssFile *child, this->values()) { |
||||||
|
news << child->getNewsList(); |
||||||
|
} |
||||||
|
return news; |
||||||
|
} |
||||||
|
|
||||||
|
QList<RssArticle*> RssFolder::getUnreadNewsList() const { |
||||||
|
QList<RssArticle*> unread_news; |
||||||
|
foreach(RssFile *child, this->values()) { |
||||||
|
unread_news << child->getUnreadNewsList(); |
||||||
|
} |
||||||
|
return unread_news; |
||||||
|
} |
||||||
|
|
||||||
|
void RssFolder::refreshStream(QString url) { |
||||||
|
qDebug("Refreshing feed: %s", url.toLocal8Bit().data()); |
||||||
|
Q_ASSERT(this->contains(url)); |
||||||
|
RssFeed *stream = (RssFeed*)this->value(url); |
||||||
|
if(stream->isLoading()) { |
||||||
|
qDebug("Stream %s is already being loaded...", stream->getUrl().toLocal8Bit().data()); |
||||||
|
return; |
||||||
|
} |
||||||
|
stream->setLoading(true); |
||||||
|
qDebug("stream %s : loaded=true", stream->getUrl().toLocal8Bit().data()); |
||||||
|
downloader->downloadUrl(url); |
||||||
|
if(!stream->hasCustomIcon()){ |
||||||
|
downloader->downloadUrl(stream->getIconUrl()); |
||||||
|
}else{ |
||||||
|
qDebug("No need to download this feed's icon, it was already downloaded"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
QList<RssFile*> RssFolder::getContent() const { |
||||||
|
return this->values(); |
||||||
|
} |
||||||
|
|
||||||
|
unsigned int RssFolder::getNbFeeds() const { |
||||||
|
unsigned int nbFeeds = 0; |
||||||
|
foreach(RssFile* item, this->values()) { |
||||||
|
if(item->getType() == RssFile::FOLDER) |
||||||
|
nbFeeds += ((RssFolder*)item)->getNbFeeds(); |
||||||
|
else |
||||||
|
nbFeeds += 1; |
||||||
|
} |
||||||
|
return nbFeeds; |
||||||
|
} |
||||||
|
|
||||||
|
void RssFolder::processFinishedDownload(QString url, QString path) { |
||||||
|
if(url.endsWith("favicon.ico")){ |
||||||
|
// Icon downloaded
|
||||||
|
QImage fileIcon; |
||||||
|
if(fileIcon.load(path)) { |
||||||
|
QList<RssFeed*> res = findFeedsWithIcon(url); |
||||||
|
RssFeed* stream; |
||||||
|
foreach(stream, res){ |
||||||
|
stream->setIconPath(path); |
||||||
|
if(!stream->isLoading()) |
||||||
|
rssmanager->forwardFeedIconChanged(stream->getUrl(), stream->getIconPath()); |
||||||
|
} |
||||||
|
}else{ |
||||||
|
qDebug("Unsupported icon format at %s", (const char*)url.toLocal8Bit()); |
||||||
|
} |
||||||
|
return; |
||||||
|
} |
||||||
|
RssFeed *stream = (RssFeed*)this->value(url, 0); |
||||||
|
if(!stream){ |
||||||
|
qDebug("This rss stream was deleted in the meantime, nothing to update"); |
||||||
|
return; |
||||||
|
} |
||||||
|
stream->processDownloadedFile(path); |
||||||
|
stream->setLoading(false); |
||||||
|
qDebug("stream %s : loaded=false", stream->getUrl().toLocal8Bit().data()); |
||||||
|
// If the feed has no alias, then we use the title as Alias
|
||||||
|
// this is more user friendly
|
||||||
|
if(stream->getName().isEmpty()){ |
||||||
|
if(!stream->getTitle().isEmpty()) |
||||||
|
stream->rename(stream->getTitle()); |
||||||
|
} |
||||||
|
rssmanager->forwardFeedInfosChanged(url, stream->getName(), stream->getNbUnRead()); |
||||||
|
} |
||||||
|
|
||||||
|
void RssFolder::handleDownloadFailure(QString url, QString reason) { |
||||||
|
if(url.endsWith("favicon.ico")){ |
||||||
|
// Icon download failure
|
||||||
|
qDebug("Could not download icon at %s, reason: %s", (const char*)url.toLocal8Bit(), (const char*)reason.toLocal8Bit()); |
||||||
|
return; |
||||||
|
} |
||||||
|
RssFeed *stream = (RssFeed*)this->value(url, 0); |
||||||
|
if(!stream){ |
||||||
|
qDebug("This rss stream was deleted in the meantime, nothing to update"); |
||||||
|
return; |
||||||
|
} |
||||||
|
stream->setLoading(false); |
||||||
|
qDebug("Could not download Rss at %s, reason: %s", (const char*)url.toLocal8Bit(), (const char*)reason.toLocal8Bit()); |
||||||
|
stream->setDownloadFailed(); |
||||||
|
rssmanager->forwardFeedInfosChanged(url, stream->getName(), stream->getNbUnRead()); |
||||||
|
} |
||||||
|
|
||||||
|
QList<RssFeed*> RssFolder::findFeedsWithIcon(QString icon_url) const { |
||||||
|
QList<RssFeed*> res; |
||||||
|
RssFile* item; |
||||||
|
foreach(item, this->values()){ |
||||||
|
if(item->getType() == RssFile::FEED && ((RssFeed*)item)->getIconUrl() == icon_url) |
||||||
|
res << (RssFeed*)item; |
||||||
|
} |
||||||
|
return res; |
||||||
|
} |
||||||
|
|
||||||
|
QString RssFolder::getName() const { |
||||||
|
return name; |
||||||
|
} |
||||||
|
|
||||||
|
void RssFolder::rename(QString new_name) { |
||||||
|
Q_ASSERT(!parent->contains(new_name)); |
||||||
|
if(!parent->contains(new_name)) { |
||||||
|
// Update parent
|
||||||
|
(*parent)[new_name] = parent->take(name); |
||||||
|
// Actually rename
|
||||||
|
name = new_name; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void RssFolder::markAllAsRead() { |
||||||
|
foreach(RssFile *item, this->values()) { |
||||||
|
item->markAllAsRead(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
QList<RssFeed*> RssFolder::getAllFeeds() const { |
||||||
|
QList<RssFeed*> streams; |
||||||
|
foreach(RssFile *item, this->values()) { |
||||||
|
if(item->getType() == RssFile::FEED) { |
||||||
|
streams << ((RssFeed*)item); |
||||||
|
} else { |
||||||
|
foreach(RssFeed* stream, ((RssFolder*)item)->getAllFeeds()) { |
||||||
|
streams << stream; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return streams; |
||||||
|
} |
||||||
|
|
||||||
|
void RssFolder::addFile(RssFile * item) { |
||||||
|
if(item->getType() == RssFile::FEED) { |
||||||
|
Q_ASSERT(!this->contains(((RssFeed*)item)->getUrl())); |
||||||
|
(*this)[((RssFeed*)item)->getUrl()] = item; |
||||||
|
qDebug("Added feed %s to folder ./%s", ((RssFeed*)item)->getUrl().toLocal8Bit().data(), name.toLocal8Bit().data()); |
||||||
|
} else { |
||||||
|
Q_ASSERT(!this->contains(((RssFolder*)item)->getName())); |
||||||
|
(*this)[((RssFolder*)item)->getName()] = item; |
||||||
|
qDebug("Added folder %s to folder ./%s", ((RssFolder*)item)->getName().toLocal8Bit().data(), name.toLocal8Bit().data()); |
||||||
|
} |
||||||
|
// Update parent
|
||||||
|
item->setParent(this); |
||||||
|
} |
||||||
|
|
||||||
|
void RssFolder::removeAllItems() { |
||||||
|
foreach(RssFile* child, values()) { |
||||||
|
child->removeAllItems(); |
||||||
|
} |
||||||
|
qDeleteAll(values()); |
||||||
|
clear(); |
||||||
|
} |
||||||
|
|
||||||
|
void RssFolder::removeAllSettings() { |
||||||
|
foreach(RssFile* child, values()) { |
||||||
|
child->removeAllSettings(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
QString RssFolder::getID() const { |
||||||
|
return name; |
||||||
|
} |
||||||
|
|
||||||
|
bool RssFolder::hasChild(QString ID) { |
||||||
|
return this->contains(ID); |
||||||
|
} |
@ -0,0 +1,87 @@ |
|||||||
|
/*
|
||||||
|
* 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 "rssfile.h" |
||||||
|
|
||||||
|
class RssArticle; |
||||||
|
class Bittorrent; |
||||||
|
class downloadThread; |
||||||
|
class RssManager; |
||||||
|
class RssFeed; |
||||||
|
|
||||||
|
class RssFolder: public RssFile, public QHash<QString, RssFile*> { |
||||||
|
Q_OBJECT |
||||||
|
|
||||||
|
public: |
||||||
|
RssFolder(RssFolder *parent, RssManager *rssmanager, Bittorrent *BTSession, QString name); |
||||||
|
~RssFolder(); |
||||||
|
RssFolder* getParent() const { return parent; } |
||||||
|
void setParent(RssFolder* _parent) { parent = _parent; } |
||||||
|
unsigned int getNbUnRead() const; |
||||||
|
FileType getType() const; |
||||||
|
RssFeed* addStream(QString url); |
||||||
|
RssFolder* addFolder(QString name); |
||||||
|
QList<RssFeed*> findFeedsWithIcon(QString icon_url) const; |
||||||
|
unsigned int getNbFeeds() const; |
||||||
|
QList<RssFile*> getContent() const; |
||||||
|
QList<RssFeed*> getAllFeeds() const; |
||||||
|
QString getName() const; |
||||||
|
QString getID() const; |
||||||
|
bool hasChild(QString ID); |
||||||
|
QList<RssArticle*> getNewsList() const; |
||||||
|
QList<RssArticle*> getUnreadNewsList() const; |
||||||
|
void removeAllSettings(); |
||||||
|
void removeAllItems(); |
||||||
|
|
||||||
|
public slots: |
||||||
|
void refreshAll(); |
||||||
|
void addFile(RssFile * item); |
||||||
|
void removeFile(QString ID); |
||||||
|
void refresh(); |
||||||
|
void refreshStream(QString url); |
||||||
|
void processFinishedDownload(QString url, QString path); |
||||||
|
void handleDownloadFailure(QString url, QString reason); |
||||||
|
void rename(QString new_name); |
||||||
|
void markAllAsRead(); |
||||||
|
|
||||||
|
private: |
||||||
|
RssFolder *parent; |
||||||
|
RssManager *rssmanager; |
||||||
|
downloadThread *downloader; |
||||||
|
Bittorrent *BTSession; |
||||||
|
QString name; |
||||||
|
}; |
||||||
|
|
||||||
|
#endif // RSSFOLDER_H
|
@ -0,0 +1,142 @@ |
|||||||
|
/*
|
||||||
|
* 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 "rssmanager.h" |
||||||
|
#include "preferences.h" |
||||||
|
#include "qbtsession.h" |
||||||
|
#include "rssfeed.h" |
||||||
|
#include "rssarticle.h" |
||||||
|
|
||||||
|
RssManager::RssManager(Bittorrent *BTSession): RssFolder(0, this, BTSession, QString::null) { |
||||||
|
loadStreamList(); |
||||||
|
connect(&newsRefresher, SIGNAL(timeout()), this, SLOT(refreshAll())); |
||||||
|
refreshInterval = Preferences::getRSSRefreshInterval(); |
||||||
|
newsRefresher.start(refreshInterval*60000); |
||||||
|
} |
||||||
|
|
||||||
|
RssManager::~RssManager(){ |
||||||
|
qDebug("Deleting RSSManager"); |
||||||
|
saveStreamList(); |
||||||
|
qDebug("RSSManager deleted"); |
||||||
|
} |
||||||
|
|
||||||
|
void RssManager::updateRefreshInterval(unsigned int val){ |
||||||
|
if(refreshInterval != val) { |
||||||
|
refreshInterval = val; |
||||||
|
newsRefresher.start(refreshInterval*60000); |
||||||
|
qDebug("New RSS refresh interval is now every %dmin", refreshInterval); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void RssManager::loadStreamList(){ |
||||||
|
QIniSettings settings("qBittorrent", "qBittorrent"); |
||||||
|
QStringList streamsUrl = settings.value("Rss/streamList").toStringList(); |
||||||
|
QStringList aliases = settings.value("Rss/streamAlias").toStringList(); |
||||||
|
if(streamsUrl.size() != aliases.size()){ |
||||||
|
std::cerr << "Corrupted Rss list, not loading it\n"; |
||||||
|
return; |
||||||
|
} |
||||||
|
unsigned int i = 0; |
||||||
|
foreach(QString s, streamsUrl){ |
||||||
|
QStringList path = s.split("\\"); |
||||||
|
if(path.empty()) continue; |
||||||
|
QString feed_url = path.takeLast(); |
||||||
|
// Create feed path (if it does not exists)
|
||||||
|
RssFolder * feed_parent = this; |
||||||
|
foreach(QString folder_name, path) { |
||||||
|
feed_parent = feed_parent->addFolder(folder_name); |
||||||
|
} |
||||||
|
// Create feed
|
||||||
|
RssFeed *stream = feed_parent->addStream(feed_url); |
||||||
|
QString alias = aliases.at(i); |
||||||
|
if(!alias.isEmpty()) { |
||||||
|
stream->rename(alias); |
||||||
|
} |
||||||
|
++i; |
||||||
|
} |
||||||
|
qDebug("NB RSS streams loaded: %d", streamsUrl.size()); |
||||||
|
} |
||||||
|
|
||||||
|
void RssManager::forwardFeedInfosChanged(QString url, QString aliasOrUrl, unsigned int nbUnread) { |
||||||
|
emit feedInfosChanged(url, aliasOrUrl, nbUnread); |
||||||
|
} |
||||||
|
|
||||||
|
void RssManager::forwardFeedIconChanged(QString url, QString icon_path) { |
||||||
|
emit feedIconChanged(url, icon_path); |
||||||
|
} |
||||||
|
|
||||||
|
void RssManager::moveFile(RssFile* file, RssFolder* dest_folder) { |
||||||
|
RssFolder* src_folder = file->getParent(); |
||||||
|
if(dest_folder != src_folder) { |
||||||
|
// Copy to new Folder
|
||||||
|
dest_folder->addFile(file); |
||||||
|
// Remove reference in old folder
|
||||||
|
src_folder->remove(file->getID()); |
||||||
|
} else { |
||||||
|
qDebug("Nothing to move, same destination folder"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void RssManager::saveStreamList(){ |
||||||
|
QStringList streamsUrl; |
||||||
|
QStringList aliases; |
||||||
|
const QList<RssFeed*> streams = getAllFeeds(); |
||||||
|
foreach(const RssFeed *stream, streams) { |
||||||
|
QString stream_path = stream->getPath().join("\\"); |
||||||
|
if(stream_path.isNull()) { |
||||||
|
stream_path = ""; |
||||||
|
} |
||||||
|
qDebug("Saving stream path: %s", qPrintable(stream_path)); |
||||||
|
streamsUrl << stream_path; |
||||||
|
aliases << stream->getName(); |
||||||
|
} |
||||||
|
QIniSettings settings("qBittorrent", "qBittorrent"); |
||||||
|
settings.beginGroup("Rss"); |
||||||
|
// FIXME: Empty folder are not saved
|
||||||
|
settings.setValue("streamList", streamsUrl); |
||||||
|
settings.setValue("streamAlias", aliases); |
||||||
|
settings.endGroup(); |
||||||
|
} |
||||||
|
|
||||||
|
void RssManager::insertSortElem(QList<RssArticle*> &list, RssArticle *item) { |
||||||
|
int i = 0; |
||||||
|
while(i < list.size() && item->getDate() < list.at(i)->getDate()) { |
||||||
|
++i; |
||||||
|
} |
||||||
|
list.insert(i, item); |
||||||
|
} |
||||||
|
|
||||||
|
QList<RssArticle*> RssManager::sortNewsList(const QList<RssArticle*>& news_list) { |
||||||
|
QList<RssArticle*> new_list; |
||||||
|
foreach(RssArticle *item, news_list) { |
||||||
|
insertSortElem(new_list, item); |
||||||
|
} |
||||||
|
return new_list; |
||||||
|
} |
@ -0,0 +1,66 @@ |
|||||||
|
/*
|
||||||
|
* 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 "rssfolder.h" |
||||||
|
|
||||||
|
class RssManager: public RssFolder { |
||||||
|
Q_OBJECT |
||||||
|
|
||||||
|
public: |
||||||
|
RssManager(Bittorrent *BTSession); |
||||||
|
~RssManager(); |
||||||
|
static void insertSortElem(QList<RssArticle*> &list, RssArticle *item); |
||||||
|
static QList<RssArticle*> sortNewsList(const QList<RssArticle*>& news_list); |
||||||
|
|
||||||
|
public slots: |
||||||
|
void loadStreamList(); |
||||||
|
void saveStreamList(); |
||||||
|
void forwardFeedInfosChanged(QString url, QString aliasOrUrl, unsigned int nbUnread); |
||||||
|
void forwardFeedIconChanged(QString url, QString icon_path); |
||||||
|
void moveFile(RssFile* file, RssFolder* dest_folder); |
||||||
|
void updateRefreshInterval(unsigned int val); |
||||||
|
|
||||||
|
signals: |
||||||
|
void feedInfosChanged(QString url, QString aliasOrUrl, unsigned int nbUnread); |
||||||
|
void feedIconChanged(QString url, QString icon_path); |
||||||
|
|
||||||
|
private: |
||||||
|
QTimer newsRefresher; |
||||||
|
unsigned int refreshInterval; |
||||||
|
Bittorrent *BTSession; |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
#endif // RSSMANAGER_H
|
Loading…
Reference in new issue