From a27d2dcac2aad651f7ad50b17562f16635533011 Mon Sep 17 00:00:00 2001 From: Christophe Dumez Date: Sat, 16 Oct 2010 17:39:03 +0000 Subject: [PATCH] Big restructuring of the RSS code Dropped Qt 4.4 support --- INSTALL | 2 +- configure | 26 +- qcm/qt4.qcm | 8 +- src/rss/feeddownloader.h | 11 +- src/rss/feedlistwidget.cpp | 22 +- src/rss/feedlistwidget.h | 8 +- src/rss/rss.cpp | 716 ------------------------------------ src/rss/rss.h | 553 ---------------------------- src/rss/rss.pri | 18 +- src/rss/rss_imp.cpp | 30 +- src/rss/rss_imp.h | 8 +- src/rss/rssarticle.cpp | 317 ++++++++++++++++ src/rss/rssarticle.h | 82 +++++ src/rss/rssfeed.cpp | 370 +++++++++++++++++++ src/rss/rssfeed.h | 104 ++++++ src/rss/rssfile.h | 72 ++++ src/rss/rssfolder.cpp | 303 +++++++++++++++ src/rss/rssfolder.h | 87 +++++ src/rss/rssmanager.cpp | 142 +++++++ src/rss/rssmanager.h | 66 ++++ src/torrentpersistentdata.h | 10 - 21 files changed, 1611 insertions(+), 1344 deletions(-) delete mode 100644 src/rss/rss.cpp delete mode 100644 src/rss/rss.h create mode 100644 src/rss/rssarticle.cpp create mode 100644 src/rss/rssarticle.h create mode 100644 src/rss/rssfeed.cpp create mode 100644 src/rss/rssfeed.h create mode 100644 src/rss/rssfile.h create mode 100644 src/rss/rssfolder.cpp create mode 100644 src/rss/rssfolder.h create mode 100644 src/rss/rssmanager.cpp create mode 100644 src/rss/rssmanager.h diff --git a/INSTALL b/INSTALL index 1f442f385..14d901a81 100644 --- a/INSTALL +++ b/INSTALL @@ -10,7 +10,7 @@ qBittorrent - A BitTorrent client in C++ / Qt4 will install and execute qBittorrent hopefully without any problems. Dependencies: - - Qt >= 4.4.0 (libqt-devel, libqtgui, libqtcore, libqtnetwork, libqtxml) + - Qt >= 4.5.0 (libqt-devel, libqtgui, libqtcore, libqtnetwork, libqtxml) - pkg-config executable diff --git a/configure b/configure index 581d12329..6e313f866 100755 --- a/configure +++ b/configure @@ -325,7 +325,7 @@ cat >$1/modules.cpp <= 4.4 +name: Qt >= 4.5 arg: disable-gui, Disable qBittorrent Graphical user interface for headless running -----END QCMOD----- */ @@ -333,14 +333,14 @@ class qc_qt4 : public ConfObj { public: qc_qt4(Conf *c) : ConfObj(c) {} - QString name() const { return "Qt >= 4.4"; } - QString shortname() const { return "Qt 4.4"; } + QString name() const { return "Qt >= 4.5"; } + QString shortname() const { return "Qt 4.5"; } bool exec() { if(!conf->getenv("QC_DISABLE_GUI").isEmpty()) { conf->addDefine("DISABLE_GUI"); } - return(QT_VERSION >= 0x040400); + return(QT_VERSION >= 0x040500); } }; #line 1 "pkg-config.qcm" @@ -616,22 +616,22 @@ arg: with-qtsingleapplication=[system|shipped], Use the shipped qtsingleapplicat class qc_qtsingleapplication : public ConfObj { public: - qc_qtsingleapplication(Conf *c) : ConfObj(c) {} - QString name() const { return "qtsingleapplication library"; } - QString shortname() const { return "qtsingleapplication"; } + qc_qtsingleapplication(Conf *c) : ConfObj(c) {} + QString name() const { return "qtsingleapplication library"; } + QString shortname() const { return "qtsingleapplication"; } - bool exec(){ - QString s; - s = conf->getenv("QC_WITH_QTSINGLEAPPLICATION"); + bool exec(){ + QString s; + s = conf->getenv("QC_WITH_QTSINGLEAPPLICATION"); if(s.compare("system", Qt::CaseInsensitive) == 0) { // System conf->addDefine("USE_SYSTEM_QTSINGLEAPPLICATION"); printf(" [system] "); } else { printf(" [shipped] "); - } - return true; - } + } + return true; + } }; EOT diff --git a/qcm/qt4.qcm b/qcm/qt4.qcm index f20a2b6ff..5cff0af06 100644 --- a/qcm/qt4.qcm +++ b/qcm/qt4.qcm @@ -1,6 +1,6 @@ /* -----BEGIN QCMOD----- -name: Qt >= 4.4 +name: Qt >= 4.5 arg: disable-gui, Disable qBittorrent Graphical user interface for headless running -----END QCMOD----- */ @@ -8,13 +8,13 @@ class qc_qt4 : public ConfObj { public: qc_qt4(Conf *c) : ConfObj(c) {} - QString name() const { return "Qt >= 4.4"; } - QString shortname() const { return "Qt 4.4"; } + QString name() const { return "Qt >= 4.5"; } + QString shortname() const { return "Qt 4.5"; } bool exec() { if(!conf->getenv("QC_DISABLE_GUI").isEmpty()) { conf->addDefine("DISABLE_GUI"); } - return(QT_VERSION >= 0x040400); + return(QT_VERSION >= 0x040500); } }; diff --git a/src/rss/feeddownloader.h b/src/rss/feeddownloader.h index 1b58b39f6..e52665182 100644 --- a/src/rss/feeddownloader.h +++ b/src/rss/feeddownloader.h @@ -41,18 +41,12 @@ #include #include #include +#include #include "qbtsession.h" #include "ui_feeddownloader.h" #include "qinisettings.h" -#if QT_VERSION >= 0x040500 -#include -#else -#include -#define QHash QMap -#define toHash toMap -#endif class FeedFilter: public QHash { private: @@ -508,7 +502,4 @@ signals: }; -#undef QHash -#undef toHash - #endif // FEEDDOWNLOADER_H diff --git a/src/rss/feedlistwidget.cpp b/src/rss/feedlistwidget.cpp index ff44f78ab..4022ada47 100644 --- a/src/rss/feedlistwidget.cpp +++ b/src/rss/feedlistwidget.cpp @@ -28,15 +28,17 @@ * Contact: chris@qbittorrent.org, arnaud@qbittorrent.org */ +# #include "feedlistwidget.h" +#include "rssmanager.h" +#include "rssfeed.h" FeedListWidget::FeedListWidget(QWidget *parent, RssManager *rssmanager): QTreeWidget(parent), rssmanager(rssmanager) { setContextMenuPolicy(Qt::CustomContextMenu); setDragDropMode(QAbstractItemView::InternalMove); setSelectionMode(QAbstractItemView::ExtendedSelection); setColumnCount(1); - QTreeWidgetItem *___qtreewidgetitem = headerItem(); - ___qtreewidgetitem->setText(0, QApplication::translate("RSS", "RSS feeds", 0, QApplication::UnicodeUTF8)); + headerItem()->setText(0, tr("RSS feeds")); unread_item = new QTreeWidgetItem(this); unread_item->setText(0, tr("Unread") + QString::fromUtf8(" (") + QString::number(rssmanager->getNbUnRead(), 10)+ QString(")")); unread_item->setData(0,Qt::DecorationRole, QVariant(QIcon(":/Icons/oxygen/mail-folder-inbox.png"))); @@ -52,18 +54,18 @@ FeedListWidget::~FeedListWidget() { void FeedListWidget::itemAdded(QTreeWidgetItem *item, RssFile* file) { mapping[item] = file; - if(file->getType() == RssFile::STREAM) { + if(file->getType() == RssFile::FEED) { feeds_items[file->getID()] = item; } } void FeedListWidget::itemAboutToBeRemoved(QTreeWidgetItem *item) { RssFile* file = mapping.take(item); - if(file->getType() == RssFile::STREAM) { + if(file->getType() == RssFile::FEED) { feeds_items.remove(file->getID()); } else { - QList feeds = ((RssFolder*)file)->getAllFeeds(); - foreach(RssStream* feed, feeds) { + QList feeds = ((RssFolder*)file)->getAllFeeds(); + foreach(RssFeed* feed, feeds) { feeds_items.remove(feed->getID()); } } @@ -121,7 +123,7 @@ QList FeedListWidget::getAllFeedItems(QTreeWidgetItem* folder) int nbChildren = folder->childCount(); for(int i=0; ichild(i); - if(getItemType(item) == RssFile::STREAM) { + if(getItemType(item) == RssFile::FEED) { feeds << item; } else { feeds << getAllFeedItems(item); @@ -146,8 +148,8 @@ QTreeWidgetItem* FeedListWidget::getTreeItemFromUrl(QString url) const{ return feeds_items.value(url, 0); } -RssStream* FeedListWidget::getRSSItemFromUrl(QString url) const { - return (RssStream*)getRSSItem(getTreeItemFromUrl(url)); +RssFeed* FeedListWidget::getRSSItemFromUrl(QString url) const { + return (RssFeed*)getRSSItem(getTreeItemFromUrl(url)); } QTreeWidgetItem* FeedListWidget::currentItem() const { @@ -161,7 +163,7 @@ QTreeWidgetItem* FeedListWidget::currentFeed() const { void FeedListWidget::updateCurrentFeed(QTreeWidgetItem* new_item) { if(!new_item) return; if(!mapping.contains(new_item)) return; - if((getItemType(new_item) == RssFile::STREAM) || new_item == unread_item) + if((getItemType(new_item) == RssFile::FEED) || new_item == unread_item) current_feed = new_item; } diff --git a/src/rss/feedlistwidget.h b/src/rss/feedlistwidget.h index 5ea3ddcb3..486ce2247 100644 --- a/src/rss/feedlistwidget.h +++ b/src/rss/feedlistwidget.h @@ -38,7 +38,11 @@ #include #include #include -#include "rss.h" + +#include "rssfile.h" + +class RssManager; +class RssFeed; class FeedListWidget: public QTreeWidget { Q_OBJECT @@ -59,7 +63,7 @@ public: RssFile::FileType getItemType(QTreeWidgetItem *item) const; QString getItemID(QTreeWidgetItem *item) const; QTreeWidgetItem* getTreeItemFromUrl(QString url) const; - RssStream* getRSSItemFromUrl(QString url) const; + RssFeed* getRSSItemFromUrl(QString url) const; QTreeWidgetItem* currentItem() const; QTreeWidgetItem* currentFeed() const; diff --git a/src/rss/rss.cpp b/src/rss/rss.cpp deleted file mode 100644 index debc006f9..000000000 --- a/src/rss/rss.cpp +++ /dev/null @@ -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 -#define QHash QMap -#define toHash toMap -#else -#include -#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 items = this->values(); - for(int i=0; igetType() == 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 RssFolder::getNewsList() const { - QList news; - foreach(RssFile *child, this->values()) { - news << child->getNewsList(); - } - return news; -} - -QList RssFolder::getUnreadNewsList() const { - QList 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 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 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 RssFolder::findFeedsWithIcon(QString icon_url) const { - QList 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 RssFolder::getAllFeeds() const { - QList 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 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 all_old_items = qBTRSS.value("old_items", QHash()).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 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 all_old_items = qBTRSS.value("old_items", QHash()).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 feeds_w_downloader = qBTRSS.value("downloader_on", QHash()).toHash(); - if(feeds_w_downloader.contains(url)) { - feeds_w_downloader.remove(url); - qBTRSS.setValue("downloader_on", feeds_w_downloader); - } - QHash all_feeds_filters = qBTRSS.value("feed_filters", QHash()).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 RssStream::getNewsList() const{ - return this->values(); -} - -QList RssStream::getUnreadNewsList() const { - QList 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 listItem = RssManager::sortNewsList(this->values()); - int excess = nb_articles - max_articles; - for(int i=0; itake(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; -} - diff --git a/src/rss/rss.h b/src/rss/rss.h deleted file mode 100644 index 61045dbe0..000000000 --- a/src/rss/rss.h +++ /dev/null @@ -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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "misc.h" -#include "feeddownloader.h" -#include "qbtsession.h" -#include "downloadthread.h" - -#if QT_VERSION >= 0x040500 -#include -#else -#include -#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 getNewsList() const = 0; - virtual QList 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 toHash() const { - QHash 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 &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 { - 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 getNewsList() const; - QList getUnreadNewsList() const; - QString getIconUrl(); - -private: - short readDoc(QIODevice* device); - void resizeList(); - short openRss(); -}; - -class RssFolder: public RssFile, public QHash { - 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 findFeedsWithIcon(QString icon_url) const; - unsigned int getNbFeeds() const; - QList getContent() const; - QList getAllFeeds() const; - QString getName() const; - QString getID() const { return name; } - bool hasChild(QString ID) { return this->contains(ID); } - QList getNewsList() const; - QList 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 &list, RssItem *item) { - int i = 0; - while(i < list.size() && item->getDate() < list.at(i)->getDate()) { - ++i; - } - list.insert(i, item); - } - - static QList sortNewsList(const QList& news_list) { - QList new_list; - foreach(RssItem *item, news_list) { - insertSortElem(new_list, item); - } - return new_list; - } - -}; - -#endif diff --git a/src/rss/rss.pri b/src/rss/rss.pri index 5e7afa4b8..b0c6d8721 100644 --- a/src/rss/rss.pri +++ b/src/rss/rss.pri @@ -2,19 +2,25 @@ INCLUDEPATH += $$PWD !contains(DEFINES, DISABLE_GUI) { - HEADERS += $$PWD/rss.h \ - $$PWD/rss_imp.h \ + HEADERS += $$PWD/rss_imp.h \ $$PWD/rsssettings.h \ $$PWD/feeddownloader.h \ $$PWD/feedlistwidget.h \ + $$PWD/rssmanager.h \ + $$PWD/rssfeed.h \ + $$PWD/rssfolder.h \ + $$PWD/rssfile.h \ + $$PWD/rssarticle.h - SOURCES += $$PWD/rss.cpp \ - $$PWD/rss_imp.cpp \ + SOURCES += $$PWD/rss_imp.cpp \ $$PWD/rsssettings.cpp \ - $$PWD/feedlistwidget.cpp + $$PWD/feedlistwidget.cpp \ + $$PWD/rssmanager.cpp \ + $$PWD/rssfeed.cpp \ + $$PWD/rssfolder.cpp \ + $$PWD/rssarticle.cpp FORMS += $$PWD/ui/rss.ui \ $$PWD/ui/feeddownloader.ui \ $$PWD/ui/rsssettings.ui - } diff --git a/src/rss/rss_imp.cpp b/src/rss/rss_imp.cpp index 694ab8072..c2d2e8f6e 100644 --- a/src/rss/rss_imp.cpp +++ b/src/rss/rss_imp.cpp @@ -44,6 +44,10 @@ #include "cookiesdlg.h" #include "preferences.h" #include "rsssettings.h" +#include "rssmanager.h" +#include "rssfolder.h" +#include "rssarticle.h" +#include "rssfeed.h" enum NewsCols { NEWS_ICON, NEWS_TITLE_COL, NEWS_URL_COL, NEWS_ID }; @@ -72,7 +76,7 @@ void RSSImp::displayRSSListMenu(const QPoint& pos){ } } myRSSListMenu.addAction(actionNew_subscription); - if(listStreams->getItemType(selectedItems.first()) == RssFile::STREAM) { + if(listStreams->getItemType(selectedItems.first()) == RssFile::FEED) { myRSSListMenu.addSeparator(); myRSSListMenu.addAction(actionCopy_feed_URL); if(selectedItems.size() == 1) { @@ -198,7 +202,7 @@ void RSSImp::on_newFeedButton_clicked() { QMessageBox::Ok); return; } - RssStream *stream = rss_parent->addStream(newUrl); + RssFeed *stream = rss_parent->addStream(newUrl); // Create TreeWidget item QTreeWidgetItem* item; if(parent_item) @@ -305,7 +309,7 @@ void RSSImp::on_updateAllButton_clicked() { void RSSImp::downloadTorrent() { QList selected_items = listNews->selectedItems(); foreach(const QTreeWidgetItem* item, selected_items) { - RssItem* article = listStreams->getRSSItemFromUrl(item->text(NEWS_URL_COL))->getItem(item->text(NEWS_ID)); + RssArticle* article = listStreams->getRSSItemFromUrl(item->text(NEWS_URL_COL))->getItem(item->text(NEWS_ID)); if(article->has_attachment()) { BTSession->downloadFromUrl(article->getTorrentUrl()); } else { @@ -318,7 +322,7 @@ void RSSImp::downloadTorrent() { void RSSImp::openNewsUrl() { QList selected_items = listNews->selectedItems(); foreach(const QTreeWidgetItem* item, selected_items) { - RssItem* news = listStreams->getRSSItemFromUrl(item->text(NEWS_URL_COL))->getItem(item->text(NEWS_ID)); + RssArticle* news = listStreams->getRSSItemFromUrl(item->text(NEWS_URL_COL))->getItem(item->text(NEWS_ID)); QString link = news->getLink(); if(!link.isEmpty()) QDesktopServices::openUrl(QUrl(link)); @@ -364,7 +368,7 @@ void RSSImp::refreshSelectedItems() { file->refresh(); break; } else { - if(file->getType() == RssFile::STREAM) { + if(file->getType() == RssFile::FEED) { item->setData(0,Qt::DecorationRole, QVariant(QIcon(":/Icons/loading.png"))); } else { // Update feeds in the folder @@ -383,7 +387,7 @@ void RSSImp::copySelectedFeedsURL() { QList selectedItems = listStreams->selectedItems(); QTreeWidgetItem* item; foreach(item, selectedItems){ - if(listStreams->getItemType(item) == RssFile::STREAM) + if(listStreams->getItemType(item) == RssFile::FEED) URLs << listStreams->getItemID(item); } qApp->clipboard()->setText(URLs.join("\n")); @@ -392,7 +396,7 @@ void RSSImp::copySelectedFeedsURL() { void RSSImp::showFeedDownloader() { QTreeWidgetItem* item = listStreams->selectedItems()[0]; RssFile* rss_item = listStreams->getRSSItem(item); - if(rss_item->getType() == RssFile::STREAM) { + if(rss_item->getType() == RssFile::FEED) { FeedDownloaderDlg* feedDownloader = new FeedDownloaderDlg(this, listStreams->getItemID(item), rss_item->getName(), BTSession); connect(feedDownloader, SIGNAL(filteringEnabled()), this, SLOT(on_updateAllButton_clicked())); } @@ -427,7 +431,7 @@ void RSSImp::fillFeedsList(QTreeWidgetItem *parent, RssFolder *rss_parent) { // Notify TreeWidget of item addition listStreams->itemAdded(item, rss_child); // Set Icon - if(rss_child->getType() == RssFile::STREAM) { + if(rss_child->getType() == RssFile::FEED) { item->setData(0,Qt::DecorationRole, QVariant(QIcon(QString::fromUtf8(":/Icons/loading.png")))); } else { item->setData(0,Qt::DecorationRole, QVariant(QIcon(QString::fromUtf8(":/Icons/oxygen/folder.png")))); @@ -448,7 +452,7 @@ void RSSImp::refreshNewsList(QTreeWidgetItem* item) { if(!rss_item) return; qDebug("Getting the list of news"); - QList news; + QList news; if(rss_item == rssmanager) news = RssManager::sortNewsList(rss_item->getUnreadNewsList()); else if(rss_item) @@ -458,7 +462,7 @@ void RSSImp::refreshNewsList(QTreeWidgetItem* item) { previous_news = 0; listNews->clear(); qDebug("Got the list of news"); - foreach(RssItem* article, news){ + foreach(RssArticle* article, news){ QTreeWidgetItem* it = new QTreeWidgetItem(listNews); it->setText(NEWS_TITLE_COL, article->getTitle()); it->setText(NEWS_URL_COL, article->getParent()->getUrl()); @@ -489,8 +493,8 @@ void RSSImp::refreshTextBrowser() { } previous_news = item; } - RssStream *stream = listStreams->getRSSItemFromUrl(item->text(NEWS_URL_COL)); - RssItem* article = stream->getItem(item->text(NEWS_ID)); + RssFeed *stream = listStreams->getRSSItemFromUrl(item->text(NEWS_URL_COL)); + RssArticle* article = stream->getItem(item->text(NEWS_ID)); QString html; html += "
"; html += "
"+article->getTitle() + "
"; @@ -557,7 +561,7 @@ void RSSImp::updateFeedIcon(QString url, QString icon_path){ void RSSImp::updateFeedInfos(QString url, QString aliasOrUrl, unsigned int nbUnread){ QTreeWidgetItem *item = listStreams->getTreeItemFromUrl(url); - RssStream *stream = (RssStream*)listStreams->getRSSItem(item); + RssFeed *stream = (RssFeed*)listStreams->getRSSItem(item); item->setText(0, aliasOrUrl + QString::fromUtf8(" (") + QString::number(nbUnread, 10)+ QString(")")); if(!stream->isLoading()) item->setData(0,Qt::DecorationRole, QVariant(QIcon(stream->getIconPath()))); diff --git a/src/rss/rss_imp.h b/src/rss/rss_imp.h index 7b54090e0..86a3e276d 100644 --- a/src/rss/rss_imp.h +++ b/src/rss/rss_imp.h @@ -35,11 +35,12 @@ #include #include "ui_rss.h" -#include "rss.h" class Bittorrent; class FeedListWidget; class QTreeWidgetItem; +class RssFolder; +class RssManager; class RSSImp : public QWidget, public Ui::RSS{ Q_OBJECT @@ -88,9 +89,4 @@ private: }; -#if QT_VERSION < 0x040500 -#undef QHash -#undef toHash -#endif - #endif diff --git a/src/rss/rssarticle.cpp b/src/rss/rssarticle.cpp new file mode 100644 index 000000000..0d06e9f4f --- /dev/null +++ b/src/rss/rssarticle.cpp @@ -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 +#include +#include + +#include + +#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 RssArticle::toHash() const { + QHash 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 &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; +} diff --git a/src/rss/rssarticle.h b/src/rss/rssarticle.h new file mode 100644 index 000000000..78c903d41 --- /dev/null +++ b/src/rss/rssarticle.h @@ -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 +#include +#include + +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 toHash() const; + static RssArticle* fromHash(RssFeed* parent, const QHash &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 diff --git a/src/rss/rssfeed.cpp b/src/rss/rssfeed.cpp new file mode 100644 index 000000000..aa6203461 --- /dev/null +++ b/src/rss/rssfeed.cpp @@ -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 all_old_items = qBTRSS.value("old_items", QHash()).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 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 all_old_items = qBTRSS.value("old_items", QHash()).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 feeds_w_downloader = qBTRSS.value("downloader_on", QHash()).toHash(); + if(feeds_w_downloader.contains(url)) { + feeds_w_downloader.remove(url); + qBTRSS.setValue("downloader_on", feeds_w_downloader); + } + QHash all_feeds_filters = qBTRSS.value("feed_filters", QHash()).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 RssFeed::getNewsList() const{ + return this->values(); +} + +QList RssFeed::getUnreadNewsList() const { + QList 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 listItem = RssManager::sortNewsList(this->values()); + int excess = nb_articles - max_articles; + for(int i=0; itake(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; +} diff --git a/src/rss/rssfeed.h b/src/rss/rssfeed.h new file mode 100644 index 000000000..55d64795c --- /dev/null +++ b/src/rss/rssfeed.h @@ -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 + +#include "rssfile.h" + +class RssManager; +class Bittorrent; + +class RssFeed: public RssFile, public QHash { + 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 getNewsList() const; + QList getUnreadNewsList() const; + QString getIconUrl(); + +private: + short readDoc(QIODevice* device); + void resizeList(); + short openRss(); +}; + + +#endif // RSSFEED_H diff --git a/src/rss/rssfile.h b/src/rss/rssfile.h new file mode 100644 index 000000000..64d15eac1 --- /dev/null +++ b/src/rss/rssfile.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 +#include + +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 getNewsList() const = 0; + virtual QList getUnreadNewsList() const = 0; + QStringList getPath() const { + QStringList path; + if(getParent()) { + path = ((RssFile*)getParent())->getPath(); + path.append(getID()); + } + return path; + } +}; + +#endif // RSSFILE_H diff --git a/src/rss/rssfolder.cpp b/src/rss/rssfolder.cpp new file mode 100644 index 000000000..11e09ad5e --- /dev/null +++ b/src/rss/rssfolder.cpp @@ -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 items = this->values(); + for(int i=0; igetType() == 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 RssFolder::getNewsList() const { + QList news; + foreach(RssFile *child, this->values()) { + news << child->getNewsList(); + } + return news; +} + +QList RssFolder::getUnreadNewsList() const { + QList 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 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 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 RssFolder::findFeedsWithIcon(QString icon_url) const { + QList 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 RssFolder::getAllFeeds() const { + QList 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); +} diff --git a/src/rss/rssfolder.h b/src/rss/rssfolder.h new file mode 100644 index 000000000..3b590906f --- /dev/null +++ b/src/rss/rssfolder.h @@ -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 + +#include "rssfile.h" + +class RssArticle; +class Bittorrent; +class downloadThread; +class RssManager; +class RssFeed; + +class RssFolder: public RssFile, public QHash { + 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 findFeedsWithIcon(QString icon_url) const; + unsigned int getNbFeeds() const; + QList getContent() const; + QList getAllFeeds() const; + QString getName() const; + QString getID() const; + bool hasChild(QString ID); + QList getNewsList() const; + QList 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 diff --git a/src/rss/rssmanager.cpp b/src/rss/rssmanager.cpp new file mode 100644 index 000000000..f57544577 --- /dev/null +++ b/src/rss/rssmanager.cpp @@ -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 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 &list, RssArticle *item) { + int i = 0; + while(i < list.size() && item->getDate() < list.at(i)->getDate()) { + ++i; + } + list.insert(i, item); +} + +QList RssManager::sortNewsList(const QList& news_list) { + QList new_list; + foreach(RssArticle *item, news_list) { + insertSortElem(new_list, item); + } + return new_list; +} diff --git a/src/rss/rssmanager.h b/src/rss/rssmanager.h new file mode 100644 index 000000000..b859f5300 --- /dev/null +++ b/src/rss/rssmanager.h @@ -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 + +#include "rssfolder.h" + +class RssManager: public RssFolder { + Q_OBJECT + +public: + RssManager(Bittorrent *BTSession); + ~RssManager(); + static void insertSortElem(QList &list, RssArticle *item); + static QList sortNewsList(const QList& 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 diff --git a/src/torrentpersistentdata.h b/src/torrentpersistentdata.h index 72bcf4318..b9eb40819 100644 --- a/src/torrentpersistentdata.h +++ b/src/torrentpersistentdata.h @@ -39,14 +39,7 @@ #include "misc.h" #include #include "qinisettings.h" - -#if QT_VERSION < 0x040500 -#include -#define QHash QMap -#define toHash toMap -#else #include -#endif class TorrentTempData { public: @@ -441,7 +434,4 @@ public: }; -#undef QHash -#undef toHash - #endif // TORRENTPERSISTENTDATA_H