From 73dbce45b2be8ff1015b1386d8868caee334f2ea Mon Sep 17 00:00:00 2001 From: Christophe Dumez Date: Sat, 22 Aug 2009 14:06:05 +0000 Subject: [PATCH] - Most of the code is there for rss feeds grouping. However it is not used yet and it is probably quite unstable --- src/GUI.cpp | 2 +- src/downloadThread.cpp | 4 + src/rss.cpp | 662 ++++++++++++++++++++++++++++++++++++++++ src/rss.h | 666 +++++++---------------------------------- src/rss.ui | 9 +- src/rss_imp.cpp | 154 ++++++---- src/rss_imp.h | 17 +- src/src.pro | 3 +- 8 files changed, 880 insertions(+), 637 deletions(-) create mode 100644 src/rss.cpp diff --git a/src/GUI.cpp b/src/GUI.cpp index 451c5fdf6..f8cf9eb3f 100644 --- a/src/GUI.cpp +++ b/src/GUI.cpp @@ -911,7 +911,7 @@ GUI::GUI(QWidget *parent, QStringList torrentCmdLine) : QMainWindow(parent), dis inDownloadList = false; break; case 3: //RSSImp - rssWidget->deleteSelectedFeeds(); + rssWidget->deleteSelectedItems(); return; default: return; diff --git a/src/downloadThread.cpp b/src/downloadThread.cpp index b13c68e55..9c2f5a7ac 100644 --- a/src/downloadThread.cpp +++ b/src/downloadThread.cpp @@ -193,9 +193,11 @@ void downloadThread::run(){ } void downloadThread::propagateDownloadedFile(subDownloadThread* st, QString url, QString path){ + mutex.lock(); int index = subThreads.indexOf(st); Q_ASSERT(index != -1); subThreads.removeAt(index); + mutex.unlock(); delete st; emit downloadFinished(url, path); mutex.lock(); @@ -206,9 +208,11 @@ void downloadThread::propagateDownloadedFile(subDownloadThread* st, QString url, } void downloadThread::propagateDownloadFailure(subDownloadThread* st, QString url, QString reason){ + mutex.lock(); int index = subThreads.indexOf(st); Q_ASSERT(index != -1); subThreads.removeAt(index); + mutex.unlock(); delete st; emit downloadFailure(url, reason); mutex.lock(); diff --git a/src/rss.cpp b/src/rss.cpp new file mode 100644 index 000000000..63bf8a6ff --- /dev/null +++ b/src/rss.cpp @@ -0,0 +1,662 @@ +/* + * 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 + +/** 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 downloader thread"); + qDeleteAll(this->values()); + delete downloader; +} + +unsigned int RssFolder::getNbUnRead() const { + // FIXME + return 0; +} + +RssFile::FileType RssFolder::getType() const { + return RssFile::FOLDER; +} + +QStringList RssFolder::getPath() const { + QStringList path; + if(parent) { + path = parent->getPath(); + path.append(name); + } + return path; +} + +void RssFolder::refreshAll(){ + qDebug("Refreshing all rss feeds"); + 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(QStringList full_path) { + QString name = full_path.last(); + if(full_path.size() == 1) { + Q_ASSERT(this->contains(name)); + delete this->take(name); + } else { + QString subfolder_name = full_path.takeFirst(); + Q_ASSERT(this->contains(subfolder_name)); + RssFolder *subfolder = (RssFolder*)this->value(subfolder_name); + subfolder->removeFile(full_path); + } +} + +RssFolder* RssFolder::addFolder(QStringList full_path) { + QString name = full_path.last(); + if(full_path.size() == 1) { + Q_ASSERT(!this->contains(name)); + RssFolder *subfolder = new RssFolder(this, rssmanager, BTSession, name); + (*this)[name] = subfolder; + } else { + QString subfolder_name = full_path.takeFirst(); + // Check if the subfolder exists and create it if it does not + if(!this->contains(subfolder_name)) { + qDebug("Creating subfolder %s which did not exist", subfolder_name.toLocal8Bit().data()); + (*this)[subfolder_name] = new RssFolder(this, rssmanager, BTSession, subfolder_name); + } + Q_ASSERT(this->contains(subfolder_name)); + RssFolder *subfolder = (RssFolder*)this->value(subfolder_name); + return subfolder->addFolder(full_path); + } +} + +RssStream* RssFolder::addStream(QStringList full_path) { + QString url = full_path.last(); + if(full_path.size() == 1) { + if(this->contains(url)){ + qDebug("Not adding the Rss stream because it is already in the list"); + return 0; + } + RssStream* stream = new RssStream(this, rssmanager, BTSession, url); + (*this)[url] = stream; + refresh(full_path); + return stream; + } else { + QString subfolder_name = full_path.takeFirst(); + // Check if the subfolder exists and create it if it does not + if(!this->contains(subfolder_name)) { + qDebug("Creating subfolder %s which did not exist", subfolder_name.toLocal8Bit().data()); + (*this)[subfolder_name] = new RssFolder(this, rssmanager, BTSession, subfolder_name); + } + Q_ASSERT(this->contains(subfolder_name)); + RssFolder *subfolder = (RssFolder*)this->value(subfolder_name); + return subfolder->addStream(full_path); + } +} + +void RssFolder::refresh(QStringList full_path) { + QString url = full_path.last(); + if(full_path.size() == 1) { + qDebug("Refreshing feed: %s", url.toLocal8Bit().data()); + Q_ASSERT(this->contains(url)); + RssStream *stream = (RssStream*)this->value(url); + if(stream->isLoading()) return; + stream->setLoading(true); + downloader->downloadUrl(url); + if(!stream->hasCustomIcon()){ + downloader->downloadUrl(stream->getIconUrl()); + }else{ + qDebug("No need to download this feed's icon, it was already downloaded"); + } + } else { + QString subfolder_name = full_path.takeFirst(); + Q_ASSERT(this->contains(subfolder_name)); + RssFolder *subfolder = (RssFolder*)this->value(subfolder_name); + subfolder->refresh(full_path); + } +} + +RssFile* RssFolder::getFile(QStringList full_path) const { + QString name = full_path.last(); + if(full_path.size() == 1) { + Q_ASSERT(this->contains(name)); + return (*this)[name]; + } else { + QString subfolder_name = full_path.takeFirst(); + Q_ASSERT(this->contains(subfolder_name)); + RssFolder *subfolder = (RssFolder*)this->value(subfolder_name); + return subfolder->getFile(full_path); + } +} + +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); + // 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(QStringList(), 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(QStringList full_path, QString new_name) { + if(full_path.size() == 1) { + name = new_name; + } else { + QString child_name = full_path.takeFirst(); + Q_ASSERT(this->contains(child_name)); + RssFile *child = (RssFile*)this->value(child_name); + if(full_path.empty()) { + // Child is renamed, update QHash + Q_ASSERT(!this->contains(new_name)); + (*this)[new_name] = this->take(child_name); + } + child->rename(full_path, 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; +} + +/** RssManager **/ + +RssManager::RssManager(bittorrent *BTSession): RssFolder(0, this, BTSession, QString::null) { + loadStreamList(); + connect(&newsRefresher, SIGNAL(timeout()), this, SLOT(refreshAll())); + QSettings settings(QString::fromUtf8("qBittorrent"), QString::fromUtf8("qBittorrent")); + refreshInterval = settings.value(QString::fromUtf8("Preferences/RSS/RSSRefresh"), 5).toInt(); + newsRefresher.start(refreshInterval*60000); +} + +RssManager::~RssManager(){ + qDebug("Deleting RSSManager"); + saveStreamList(); + qDebug("RSSManager deleted"); +} + +void RssManager::loadStreamList(){ + QSettings 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; + RssStream *stream = this->addStream(path); + QString alias = aliases.at(i); + if(!alias.isEmpty()) { + stream->rename(QStringList(), 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::saveStreamList(){ + QList > streamsList; + QStringList streamsUrl; + QStringList aliases; + QList streams = getAllFeeds(); + foreach(RssStream *stream, streams) { + streamsUrl << stream->getPath().join("\\"); + aliases << stream->getName(); + } + QSettings 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), url(_url), alias(""), iconPath(":/Icons/rss16.png"), refreshed(false), downloadFailure(false), currently_loading(false) { + qDebug("RSSStream constructed"); + QSettings qBTRSS("qBittorrent", "qBittorrent-rss"); + 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(item); + if(rss_item->isValid()) + listItem << rss_item; + } +} + +RssStream::~RssStream(){ + if(refreshed) { + QSettings qBTRSS("qBittorrent", "qBittorrent-rss"); + QVariantList old_items; + foreach(RssItem *item, listItem) { + 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); + } + removeAllItems(); + if(QFile::exists(filePath)) + QFile::remove(filePath); + if(QFile::exists(iconPath) && !iconPath.startsWith(":/")) + QFile::remove(iconPath); +} + +RssFile::FileType RssStream::getType() const { + return RssFile::STREAM; +} + +void RssStream::refresh() { + QStringList path; + path << url; + parent->refresh(path); +} + +QStringList RssStream::getPath() const { + QStringList path = parent->getPath(); + path.append(url); + return path; +} + +// delete all the items saved +void RssStream::removeAllItems() { + qDeleteAll(listItem); + listItem.clear(); +} + +bool RssStream::itemAlreadyExists(QString hash) { + RssItem * item; + foreach(item, listItem) { + if(item->getHash() == hash) return true; + } + return false; +} + +void RssStream::setLoading(bool val) { + currently_loading = val; +} + +bool RssStream::isLoading() { + return currently_loading; +} + +QString RssStream::getTitle() const{ + return title; +} + +void RssStream::rename(QStringList, 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(unsigned int index) const{ + return listItem.at(index); +} + +unsigned int RssStream::getNbNews() const{ + return listItem.size(); +} + +void RssStream::markAllAsRead() { + RssItem *item; + foreach(item, listItem){ + if(!item->isRead()) + item->setRead(); + } +} + +unsigned int RssStream::getNbUnRead() const{ + unsigned int nbUnread=0; + RssItem *item; + foreach(item, listItem){ + if(!item->isRead()) + ++nbUnread; + } + return nbUnread; +} + +QList RssStream::getNewsList() const{ + return listItem; +} + +// 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(const QDomDocument& doc) { + // is it a rss file ? + QDomElement root = doc.documentElement(); + if(root.tagName() == QString::fromUtf8("html")){ + qDebug("the file is empty, maybe the url is invalid or the server is too busy"); + return -1; + } + else if(root.tagName() != QString::fromUtf8("rss")){ + qDebug("the file is not a rss stream, omitted: %s", root.tagName().toLocal8Bit().data()); + return -1; + } + QDomNode rss = root.firstChild(); + QDomElement channel = root.firstChild().toElement(); + + while(!channel.isNull()) { + // we are reading the rss'main info + if (channel.tagName() == "channel") { + QDomElement property = channel.firstChild().toElement(); + while(!property.isNull()) { + if (property.tagName() == "title") { + title = property.text(); + if(alias==getUrl()) + rename(QStringList(), title); + } + else if (property.tagName() == "link") + link = property.text(); + else if (property.tagName() == "description") + description = property.text(); + else if (property.tagName() == "image") + image = property.text(); + else if(property.tagName() == "item") { + RssItem * item = new RssItem(property); + if(item->isValid() && !itemAlreadyExists(item->getHash())) { + listItem.append(item); + // 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(item->getTorrentUrl()); + else + BTSession->downloadUrlAndSkipDialog(item->getTorrentUrl(), save_path); + } else { + // All torrents are downloaded from this feed + BTSession->downloadUrlAndSkipDialog(item->getTorrentUrl()); + } + // Item was downloaded, consider it as Read + item->setRead(); + // Clean up + delete matching_filter; + } + } else { + delete item; + } + } + property = property.nextSibling().toElement(); + } + } + channel = channel.nextSibling().toElement(); + } + sortList(); + resizeList(); + return 0; +} + +void RssStream::insertSortElem(QList &list, RssItem *item) { + int i = 0; + while(i < list.size() && item->getDate() < list.at(i)->getDate()) { + ++i; + } + list.insert(i, item); +} + +void RssStream::sortList() { + QList new_list; + RssItem *item; + foreach(item, listItem) { + insertSortElem(new_list, item); + } + listItem = new_list; +} + +void RssStream::resizeList() { + QSettings settings(QString::fromUtf8("qBittorrent"), QString::fromUtf8("qBittorrent")); + unsigned int max_articles = settings.value(QString::fromUtf8("Preferences/RSS/RSSMaxArticlesPerFeed"), 100).toInt(); + int excess = listItem.size() - max_articles; + if(excess <= 0) return; + for(int i=0; i= 0) { + refreshed = true; + } else { + qDebug("OpenRss: Feed update Failed"); + } +} + +void RssStream::setDownloadFailed(){ + downloadFailure = true; +} diff --git a/src/rss.h b/src/rss.h index bf1c26b26..e449c6b17 100644 --- a/src/rss.h +++ b/src/rss.h @@ -25,7 +25,7 @@ * 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 + * Contact: chris@qbittorrent.org, arnaud@qbittorrent.org */ #ifndef RSS_H @@ -50,6 +50,8 @@ #include "downloadThread.h" class RssManager; +class RssFile; // Folder or Stream +class RssFolder; class RssStream; class RssItem; @@ -75,8 +77,24 @@ static const char longMonth[][10] = { "October", "November", "December" }; +class RssFile: public QObject { + Q_OBJECT + +public: + enum FileType {STREAM, FOLDER}; + + RssFile(): QObject() {} + + virtual unsigned int getNbUnRead() const = 0; + virtual FileType getType() const = 0; + virtual QStringList getPath() const = 0; + virtual QString getName() const = 0; + virtual void rename(QStringList path, QString new_name) = 0; + virtual void markAllAsRead() = 0; +}; + // Item of a rss stream, single information -class RssItem : public QObject { +class RssItem: public QObject { Q_OBJECT private: @@ -161,7 +179,7 @@ protected: 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; + year += (i == 2 && year < 50) ? 2000: 1900; } // Parse the UTC offset part @@ -342,10 +360,12 @@ public: }; // Rss stream, loaded form an xml file -class RssStream : public QObject{ +class RssStream: public RssFile { Q_OBJECT private: + RssFolder *parent; + RssManager *rssmanager; bittorrent *BTSession; QString title; QString link; @@ -356,579 +376,107 @@ private: QString filePath; QString iconPath; QList listItem; - QTime lastRefresh; bool read; bool refreshed; bool downloadFailure; bool currently_loading; -public slots : - // read and store the downloaded rss' informations - void processDownloadedFile(QString file_path) { - filePath = file_path; - downloadFailure = false; - lastRefresh.start(); - if(openRss() >= 0) { - refreshed = true; - } else { - qDebug("OpenRss: Feed update Failed"); - } -} +public slots: + void processDownloadedFile(QString file_path); + void setDownloadFailed(); -void setDownloadFailed(){ - downloadFailure = true; - lastRefresh.start(); -} +public: + RssStream(RssFolder* parent, RssManager *rssmanager, bittorrent *BTSession, QString _url); + ~RssStream(); + FileType getType() const; + void refresh(); + QStringList getPath() const; + void removeAllItems(); + bool itemAlreadyExists(QString hash); + void setLoading(bool val); + bool isLoading(); + QString getTitle() const; + void rename(QStringList path, 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(unsigned int index) const; + unsigned int getNbNews() const; + void markAllAsRead(); + unsigned int getNbUnRead() const; + QList getNewsList() const; + QString getIconUrl(); - public: -RssStream(bittorrent *BTSession, QString _url): BTSession(BTSession), url(_url), alias(""), iconPath(":/Icons/rss16.png"), refreshed(false), downloadFailure(false), currently_loading(false) { - qDebug("RSSStream constructed"); - QSettings qBTRSS("qBittorrent", "qBittorrent-rss"); - 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(), getAliasOrUrl().toLocal8Bit().data()); - foreach(const QVariant &var_it, old_items) { - QHash item = var_it.toHash(); - RssItem *rss_item = RssItem::fromHash(item); - if(rss_item->isValid()) - listItem << rss_item; - } -} - -~RssStream(){ - if(refreshed) { - QSettings qBTRSS("qBittorrent", "qBittorrent-rss"); - QVariantList old_items; - foreach(RssItem *item, listItem) { - old_items << item->toHash(); - } - qDebug("Saving %d old items for feed %s", old_items.size(), getAliasOrUrl().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); - } - removeAllItems(); - if(QFile::exists(filePath)) - QFile::remove(filePath); - if(QFile::exists(iconPath) && !iconPath.startsWith(":/")) - QFile::remove(iconPath); -} - -// delete all the items saved -void removeAllItems() { - qDeleteAll(listItem); - listItem.clear(); -} - -bool itemAlreadyExists(QString hash) { - RssItem * item; - foreach(item, listItem) { - if(item->getHash() == hash) return true; - } - return false; -} - -void setLoading(bool val) { - currently_loading = val; -} - -bool isLoading() { - return currently_loading; -} - -QString getTitle() const{ - return title; -} - -QString getAlias() const{ - qDebug("getAlias() returned Alias: %s", (const char*)alias.toLocal8Bit()); - return alias; -} - -void setAlias(QString _alias){ - qDebug("setAlias() to %s", (const char*)_alias.toLocal8Bit()); - alias = _alias; -} - -// Return the alias if the stream has one, the url if it has no alias -QString getAliasOrUrl() const{ - if(!alias.isEmpty()) { - qDebug("getAliasOrUrl() returned alias: %s", (const char*)alias.toLocal8Bit()); - return alias; - } - if(!title.isEmpty()) { - qDebug("getAliasOrUrl() returned title: %s", (const char*)title.toLocal8Bit()); - return title; - } - qDebug("getAliasOrUrl() returned url: %s", (const char*)url.toLocal8Bit()); - return url; -} - -QString getLink() const{ - return link; -} - -QString getUrl() const{ - return url; -} - -QString getDescription() const{ - return description; -} - -QString getImage() const{ - return image; -} - -QString getFilePath() const{ - return filePath; -} - -QString getIconPath() const{ - if(downloadFailure) - return ":/Icons/oxygen/unavailable.png"; - return iconPath; -} - -bool hasCustomIcon() const{ - return !iconPath.startsWith(":/"); -} - -void setIconPath(QString path) { - iconPath = path; -} - -RssItem* getItem(unsigned int index) const{ - return listItem.at(index); -} - -unsigned int getNbNews() const{ - return listItem.size(); -} - -void markAllAsRead() { - RssItem *item; - foreach(item, listItem){ - if(!item->isRead()) - item->setRead(); - } -} - -unsigned int getNbUnRead() const{ - unsigned int nbUnread=0; - RssItem *item; - foreach(item, listItem){ - if(!item->isRead()) - ++nbUnread; - } - return nbUnread; -} - -QList getNewsList() const{ - return listItem; -} - -QString getLastRefreshElapsedString() const{ - if(!refreshed) - return tr("Never"); - return tr("%1 ago", "10min ago").arg(misc::userFriendlyDuration((long)(lastRefresh.elapsed()/1000.)).replace("<", "<")); -} - -int getLastRefreshElapsed() const{ - if(!refreshed) - return -1; - return lastRefresh.elapsed(); -} - -// download the icon from the adress -QString getIconUrl() { - QUrl siteUrl(url); - return QString::fromUtf8("http://")+siteUrl.host()+QString::fromUtf8("/favicon.ico"); -} - - private: -// read and create items from a rss document -short readDoc(const QDomDocument& doc) { - // is it a rss file ? - QDomElement root = doc.documentElement(); - if(root.tagName() == QString::fromUtf8("html")){ - qDebug("the file is empty, maybe the url is invalid or the server is too busy"); - return -1; - } - else if(root.tagName() != QString::fromUtf8("rss")){ - qDebug("the file is not a rss stream, omitted: %s", root.tagName().toLocal8Bit().data()); - return -1; - } - QDomNode rss = root.firstChild(); - QDomElement channel = root.firstChild().toElement(); - - while(!channel.isNull()) { - // we are reading the rss'main info - if (channel.tagName() == "channel") { - QDomElement property = channel.firstChild().toElement(); - while(!property.isNull()) { - if (property.tagName() == "title") { - title = property.text(); - if(alias==getUrl()) - setAlias(title); - } - else if (property.tagName() == "link") - link = property.text(); - else if (property.tagName() == "description") - description = property.text(); - else if (property.tagName() == "image") - image = property.text(); - else if(property.tagName() == "item") { - RssItem * item = new RssItem(property); - if(item->isValid() && !itemAlreadyExists(item->getHash())) { - listItem.append(item); - // 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(getAliasOrUrl())); - if(matching_filter->isValid()) { - QString save_path = matching_filter->getSavePath(); - if(save_path.isEmpty()) - BTSession->downloadUrlAndSkipDialog(item->getTorrentUrl()); - else - BTSession->downloadUrlAndSkipDialog(item->getTorrentUrl(), save_path); - } else { - // All torrents are downloaded from this feed - BTSession->downloadUrlAndSkipDialog(item->getTorrentUrl()); - } - // Item was downloaded, consider it as Read - item->setRead(); - // Clean up - delete matching_filter; - } - } else { - delete item; - } - } - property = property.nextSibling().toElement(); - } - } - channel = channel.nextSibling().toElement(); - } - sortList(); - resizeList(); - return 0; -} - -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); -} - -void sortList() { - QList new_list; - RssItem *item; - foreach(item, listItem) { - insertSortElem(new_list, item); - } - listItem = new_list; -} - -void resizeList() { - QSettings settings(QString::fromUtf8("qBittorrent"), QString::fromUtf8("qBittorrent")); - unsigned int max_articles = settings.value(QString::fromUtf8("Preferences/RSS/RSSMaxArticlesPerFeed"), 100).toInt(); - int excess = listItem.size() - max_articles; - if(excess <= 0) return; - for(int i=0; i &list, RssItem *item); + void sortList(); + void resizeList(); + short openRss(); }; -// global class, manage the whole rss stream -class RssManager : public QObject{ +class RssFolder: public RssFile, public QHash { Q_OBJECT -private : - QHash streams; -downloadThread *downloader; -QTimer newsRefresher; -unsigned int refreshInterval; -bittorrent *BTSession; +private: + RssFolder *parent; + RssManager *rssmanager; + downloadThread *downloader; + bittorrent *BTSession; + QString name; - signals: -void feedInfosChanged(QString url, QString aliasOrUrl, unsigned int nbUnread); -void feedIconChanged(QString url, QString icon_path); +public: + RssFolder(RssFolder *parent, RssManager *rssmanager, bittorrent *BTSession, QString name); + ~RssFolder(); + unsigned int getNbUnRead() const; + FileType getType() const; + RssStream* addStream(QStringList full_path); + RssFolder* addFolder(QStringList full_path); + QList findFeedsWithIcon(QString icon_url) const; + unsigned int getNbFeeds() const; + QList getContent() const; + RssFile* getFile(QStringList full_path) const; + QList getAllFeeds() const; + QString getName() const; + QStringList getPath() const; - public slots : +public slots: + void refreshAll(); + void removeFile(QStringList full_path); + void refresh(QStringList full_path); + void processFinishedDownload(QString url, QString path); + void handleDownloadFailure(QString url, QString reason); + void rename(QStringList full_path, QString new_name); + void markAllAsRead(); +}; - void 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()) - emit feedIconChanged(stream->getUrl(), stream->getIconPath()); - } - }else{ - qDebug("Unsupported icon format at %s", (const char*)url.toLocal8Bit()); - } - return; - } - RssStream *stream = streams.value(url, 0); - if(!stream){ - qDebug("This rss stream was deleted in the meantime, nothing to update"); - return; - } - stream->processDownloadedFile(path); - stream->setLoading(false); - // If the feed has no alias, then we use the title as Alias - // this is more user friendly - if(stream->getAlias().isEmpty()){ - if(!stream->getTitle().isEmpty()) - stream->setAlias(stream->getTitle()); - } - emit feedInfosChanged(url, stream->getAliasOrUrl(), stream->getNbUnRead()); - } +class RssManager: public RssFolder{ + Q_OBJECT - void 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 = streams.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(); - emit feedInfosChanged(url, stream->getAliasOrUrl(), stream->getNbUnRead()); - } +private: + QTimer newsRefresher; + unsigned int refreshInterval; + bittorrent *BTSession; - void refreshOldFeeds(){ - qDebug("refresh old feeds"); - RssStream *stream; - foreach(stream, streams){ - QString url = stream->getUrl(); - if(stream->isLoading()) continue; - if(stream->getLastRefreshElapsed() != -1 && stream->getLastRefreshElapsed() < (int)refreshInterval) continue; - qDebug("Refreshing old feed: %s...", (const char*)url.toLocal8Bit()); - stream->setLoading(true); - downloader->downloadUrl(url); - if(!stream->hasCustomIcon()){ - downloader->downloadUrl(stream->getIconUrl()); - } - } - // See if refreshInterval has changed - QSettings settings(QString::fromUtf8("qBittorrent"), QString::fromUtf8("qBittorrent")); - unsigned int new_refreshInterval = settings.value(QString::fromUtf8("Preferences/RSS/RSSRefresh"), 5).toInt(); - if(new_refreshInterval != refreshInterval) { - refreshInterval = new_refreshInterval; - newsRefresher.start(refreshInterval*60000); - } - } +signals: + void feedInfosChanged(QString url, QString aliasOrUrl, unsigned int nbUnread); + void feedIconChanged(QString url, QString icon_path); -public : - RssManager(bittorrent *BTSession): BTSession(BTSession){ - 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))); - loadStreamList(); - connect(&newsRefresher, SIGNAL(timeout()), this, SLOT(refreshOldFeeds())); - QSettings settings(QString::fromUtf8("qBittorrent"), QString::fromUtf8("qBittorrent")); - refreshInterval = settings.value(QString::fromUtf8("Preferences/RSS/RSSRefresh"), 5).toInt(); - newsRefresher.start(refreshInterval*60000); -} +public slots: + void loadStreamList(); + void saveStreamList(); + void forwardFeedInfosChanged(QString url, QString aliasOrUrl, unsigned int nbUnread); + void forwardFeedIconChanged(QString url, QString icon_path); -~RssManager(){ - qDebug("Deleting RSSManager"); - saveStreamList(); - qDebug("Deleting all streams"); - qDeleteAll(streams); - qDebug("Deleting downloader thread"); - delete downloader; - qDebug("RSSManager deleted"); -} - -// load the list of the rss stream -void loadStreamList(){ - QSettings 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; - } - QString url; - unsigned int i = 0; - foreach(url, streamsUrl){ - RssStream *stream = new RssStream(BTSession, url); - QString alias = aliases.at(i); - if(!alias.isEmpty()) { - stream->setAlias(alias); - } - streams[url] = stream; - ++i; - } - qDebug("NB RSS streams loaded: %d", streamsUrl.size()); -} - -// save the list of the rss stream for the next session -void saveStreamList(){ - QList > streamsList; - QStringList streamsUrl; - QStringList aliases; - RssStream *stream; - foreach(stream, streams){ - streamsUrl << stream->getUrl(); - aliases << stream->getAlias(); - } - QSettings settings("qBittorrent", "qBittorrent"); - settings.beginGroup("Rss"); - settings.setValue("streamList", streamsUrl); - settings.setValue("streamAlias", aliases); - settings.endGroup(); -} - -// add a stream to the manager -void addStream(RssStream* stream){ - QString url = stream->getUrl(); - if(streams.contains(url)){ - qDebug("Not adding the Rss stream because it is already in the list"); - return; - } - streams[url] = stream; - emit feedIconChanged(url, stream->getIconPath()); -} - -// add a stream to the manager -RssStream* addStream(QString url){ - if(streams.contains(url)){ - qDebug("Not adding the Rss stream because it is already in the list"); - return 0; - } - RssStream* stream = new RssStream(BTSession, url); - streams[url] = stream; - refresh(url); - return stream; -} - -// remove a stream from the manager -void removeStream(RssStream* stream){ - QString url = stream->getUrl(); - Q_ASSERT(streams.contains(url)); - delete streams.take(url); -} - -QList findFeedsWithIcon(QString icon_url){ - QList res; - RssStream* stream; - foreach(stream, streams){ - if(stream->getIconUrl() == icon_url) - res << stream; - } - return res; -} - -void removeStream(QString url){ - Q_ASSERT(streams.contains(url)); - delete streams.take(url); -} - -// remove all the streams in the manager -void removeAll(){ - qDeleteAll(streams); - streams.clear(); -} - -// reload all the xml files from the web -void refreshAll(){ - qDebug("Refreshing all rss feeds"); - RssStream *stream; - foreach(stream, streams){ - QString url = stream->getUrl(); - if(stream->isLoading()) return; - qDebug("Refreshing feed: %s...", (const char*)url.toLocal8Bit()); - stream->setLoading(true); - downloader->downloadUrl(url); - if(!stream->hasCustomIcon()){ - downloader->downloadUrl(stream->getIconUrl()); - } - } -} - -void refresh(QString url) { - qDebug("Refreshing feed: %s", url.toLocal8Bit().data()); - Q_ASSERT(streams.contains(url)); - RssStream *stream = streams[url]; - if(stream->isLoading()) return; - stream->setLoading(true); - downloader->downloadUrl(url); - if(!stream->hasCustomIcon()){ - downloader->downloadUrl(stream->getIconUrl()); - }else{ - qDebug("No need to download this feed's icon, it was already downloaded"); - } -} - -// XXX: Used? -unsigned int getNbFeeds() { - return streams.size(); -} - -RssStream* getFeed(QString url){ - Q_ASSERT(streams.contains(url)); - return streams[url]; -} - -// Set an alias for a stream and save it for later -void setAlias(QString url, QString newAlias) { - Q_ASSERT(!newAlias.isEmpty()); - RssStream * stream = streams.value(url, 0); - Q_ASSERT(stream != 0); - stream->setAlias(newAlias); - emit feedInfosChanged(url, stream->getAliasOrUrl(), stream->getNbUnRead()); -} - -// Return all the rss feeds we have -QList getRssFeeds() const { - return streams.values(); -} +public: + RssManager(bittorrent *BTSession); + ~RssManager(); }; diff --git a/src/rss.ui b/src/rss.ui index f9a9d7abd..a970117a8 100644 --- a/src/rss.ui +++ b/src/rss.ui @@ -101,7 +101,7 @@ QAbstractItemView::ExtendedSelection - 2 + 3 @@ -110,7 +110,12 @@ - 2 + url + + + + + type diff --git a/src/rss_imp.cpp b/src/rss_imp.cpp index 197eac109..b29ca18cd 100644 --- a/src/rss_imp.cpp +++ b/src/rss_imp.cpp @@ -33,12 +33,10 @@ #include #include #include -#include #include #include #include "rss_imp.h" -#include "rss.h" #include "FeedDownloader.h" #include "bittorrent.h" @@ -82,8 +80,35 @@ void RSSImp::displayItemsListMenu(const QPoint&){ myItemListMenu.exec(QCursor::pos()); } +QStringList RSSImp::getItemPath(QTreeWidgetItem *item) const { + QStringList path; + if(item) { + if(item->parent()) { + path = getItemPath(item->parent()); + } + path << item->text(1); + } + return path; +} + +QStringList RSSImp::getCurrentFeedPath() const { + return getItemPath(listStreams->currentItem()); +} + +RssFile::FileType RSSImp::getItemType(QTreeWidgetItem *item) const { + if(!item) + return RssFile::FOLDER; + return (RssFile::FileType)item->text(2).toInt(); +} + // add a stream by a button void RSSImp::on_newFeedButton_clicked() { + QStringList dest_path; + QTreeWidgetItem *current_item = listStreams->currentItem(); + if(getItemType(current_item) != RssFile::FOLDER) + dest_path = getItemPath(current_item->parent()); + else + dest_path = getItemPath(current_item); bool ok; QString clip_txt = qApp->clipboard()->text(); QString default_url = "http://"; @@ -94,7 +119,8 @@ void RSSImp::on_newFeedButton_clicked() { if(ok) { newUrl = newUrl.trimmed(); if(!newUrl.isEmpty()){ - RssStream *stream = rssmanager->addStream(newUrl); + dest_path.append(newUrl); + RssStream *stream = rssmanager->addStream(dest_path); if(stream == 0){ // Already existing QMessageBox::warning(this, tr("qBittorrent"), @@ -103,24 +129,23 @@ void RSSImp::on_newFeedButton_clicked() { return; } QTreeWidgetItem* item = new QTreeWidgetItem(listStreams); - item->setText(0, stream->getAliasOrUrl() + QString::fromUtf8(" (0)")); + item->setText(0, stream->getName() + QString::fromUtf8(" (0)")); item->setText(1, stream->getUrl()); item->setData(0,Qt::DecorationRole, QVariant(QIcon(":/Icons/loading.png"))); - item->setToolTip(0, QString::fromUtf8("")+tr("Description:")+QString::fromUtf8(" ")+stream->getDescription()+QString::fromUtf8("
")+tr("url:")+QString::fromUtf8(" ")+stream->getUrl()+QString::fromUtf8("
")+tr("Last refresh:")+QString::fromUtf8(" ")+stream->getLastRefreshElapsedString()); if(listStreams->topLevelItemCount() == 1) selectFirstFeed(); - rssmanager->refresh(newUrl); + stream->refresh(); rssmanager->saveStreamList(); } } } // delete a stream by a button -void RSSImp::deleteSelectedFeeds() { +void RSSImp::deleteSelectedItems() { QList selectedItems = listStreams->selectedItems(); if(selectedItems.size() == 0) return; if(!selectedItems.size()) return; - int ret = QMessageBox::question(this, tr("Are you sure? -- qBittorrent"), tr("Are you sure you want to delete this stream from the list?"), + int ret = QMessageBox::question(this, tr("Are you sure? -- qBittorrent"), tr("Are you sure you want to delete this RSS feed from the list?"), tr("&Yes"), tr("&No"), QString(), 0, 1); if(!ret) { @@ -129,7 +154,7 @@ void RSSImp::deleteSelectedFeeds() { textBrowser->clear(); listNews->clear(); } - rssmanager->removeStream(item->text(1)); + rssmanager->removeFile(getItemPath(item)); delete item; } rssmanager->saveStreamList(); @@ -142,13 +167,12 @@ void RSSImp::on_updateAllButton_clicked() { for(unsigned int i=0; itopLevelItem(i)->setData(0,Qt::DecorationRole, QVariant(QIcon(":/Icons/loading.png"))); rssmanager->refreshAll(); - updateLastRefreshedTimeForStreams(); } void RSSImp::downloadTorrent() { QList selected_items = listNews->selectedItems(); foreach(const QListWidgetItem* item, selected_items) { - RssItem* news = rssmanager->getFeed(getCurrentFeedUrl())->getItem(listNews->row(item)); + RssItem* news = ((RssStream*)rssmanager->getFile(getCurrentFeedPath()))->getItem(listNews->row(item)); BTSession->downloadFromUrl(news->getTorrentUrl()); } } @@ -157,7 +181,7 @@ void RSSImp::downloadTorrent() { void RSSImp::openNewsUrl() { QList selected_items = listNews->selectedItems(); foreach(const QListWidgetItem* item, selected_items) { - RssItem* news = rssmanager->getFeed(getCurrentFeedUrl())->getItem(listNews->row(item)); + RssItem* news = ((RssStream*)rssmanager->getFile(getCurrentFeedPath()))->getItem(listNews->row(item)); QString link = news->getLink(); if(!link.isEmpty()) QDesktopServices::openUrl(QUrl(link)); @@ -165,15 +189,14 @@ void RSSImp::openNewsUrl() { } //right-click on stream : give him an alias -void RSSImp::renameStream() { +void RSSImp::renameFiles() { QList selectedItems = listStreams->selectedItems(); Q_ASSERT(selectedItems.size() == 1); QTreeWidgetItem *item = selectedItems.at(0); - QString url = item->data(1, Qt::DisplayRole).toString(); bool ok; - QString newAlias = QInputDialog::getText(this, tr("Please choose a new name for this stream"), tr("New stream name:"), QLineEdit::Normal, rssmanager->getFeed(url)->getAlias(), &ok); + QString newName = QInputDialog::getText(this, tr("Please choose a new name for this RSS feed"), tr("New feed name:"), QLineEdit::Normal, rssmanager->getFile(getItemPath(item))->getName(), &ok); if(ok) { - rssmanager->setAlias(url, newAlias); + rssmanager->rename(getItemPath(item), newName); } } @@ -182,9 +205,9 @@ void RSSImp::refreshSelectedStreams() { QList selectedItems = listStreams->selectedItems(); QTreeWidgetItem* item; foreach(item, selectedItems){ - QString url = item->text(1); - rssmanager->refresh(url); - item->setData(0,Qt::DecorationRole, QVariant(QIcon(":/Icons/loading.png"))); + rssmanager->refresh(getItemPath(item)); + if(getItemType(item) == RssFile::STREAM) + item->setData(0,Qt::DecorationRole, QVariant(QIcon(":/Icons/loading.png"))); } } @@ -200,40 +223,48 @@ void RSSImp::copySelectedFeedsURL() { void RSSImp::showFeedDownloader() { QTreeWidgetItem* item = listStreams->selectedItems()[0]; - new FeedDownloaderDlg(this, item->text(1), rssmanager->getFeed(item->text(1))->getAliasOrUrl(), BTSession); + RssFile* rss_item = rssmanager->getFile(getItemPath(item)); + if(rss_item->getType() == RssFile::STREAM) + new FeedDownloaderDlg(this, item->text(1), rss_item->getName(), BTSession); } void RSSImp::on_markReadButton_clicked() { QList selectedItems = listStreams->selectedItems(); QTreeWidgetItem* item; foreach(item, selectedItems){ - QString url = item->text(1); - RssStream *feed = rssmanager->getFeed(url); - feed->markAllAsRead(); - item->setData(0, Qt::DisplayRole, feed->getAliasOrUrl()+ QString::fromUtf8(" (0)")); + RssFile *rss_item = rssmanager->getFile(getItemPath(item)); + rss_item->markAllAsRead(); + item->setData(0, Qt::DisplayRole, rss_item->getName()+ QString::fromUtf8(" (0)")); } if(selectedItems.size()) refreshNewsList(listStreams->currentItem()); } -void RSSImp::fillFeedsList() { - QList feeds = rssmanager->getRssFeeds(); - RssStream* stream; - foreach(stream, feeds){ - QTreeWidgetItem* item = new QTreeWidgetItem(listStreams); - item->setData(0, Qt::DisplayRole, stream->getAliasOrUrl()+ QString::fromUtf8(" (")+QString::number(stream->getNbUnRead(), 10)+QString(")")); - item->setData(0,Qt::DecorationRole, QVariant(QIcon(QString::fromUtf8(":/Icons/loading.png")))); - item->setData(1, Qt::DisplayRole, stream->getUrl()); - item->setToolTip(0, QString::fromUtf8("")+tr("Description:")+QString::fromUtf8(" ")+stream->getDescription()+QString::fromUtf8("
")+tr("url:")+QString::fromUtf8(" ")+stream->getUrl()+QString::fromUtf8("
")+tr("Last refresh:")+QString::fromUtf8(" ")+stream->getLastRefreshElapsedString()); +void RSSImp::fillFeedsList(QTreeWidgetItem *parent, RssFolder *rss_parent) { + QList children; + if(parent) { + children = rss_parent->getContent(); + } else { + children = rssmanager->getContent(); } -} - -void RSSImp::updateLastRefreshedTimeForStreams() { - unsigned int nbFeeds = listStreams->topLevelItemCount(); - for(unsigned int i=0; itopLevelItem(i); - RssStream* stream = rssmanager->getFeed(item->data(1, Qt::DisplayRole).toString()); - item->setToolTip(0, QString::fromUtf8("")+tr("Description:")+QString::fromUtf8(" ")+stream->getDescription()+QString::fromUtf8("
")+tr("url:")+QString::fromUtf8(" ")+stream->getUrl()+QString::fromUtf8("
")+tr("Last refresh:")+QString::fromUtf8(" ")+stream->getLastRefreshElapsedString()); + foreach(RssFile* rss_child, children){ + QTreeWidgetItem* item; + if(!parent) + item = new QTreeWidgetItem(listStreams); + else + item = new QTreeWidgetItem(parent); + item->setData(0, Qt::DisplayRole, rss_child->getName()+ QString::fromUtf8(" (")+QString::number(rss_child->getNbUnRead(), 10)+QString(")")); + if(rss_child->getType() == RssFile::STREAM) { + item->setData(0,Qt::DecorationRole, QVariant(QIcon(QString::fromUtf8(":/Icons/loading.png")))); + item->setData(1, Qt::DisplayRole, ((RssStream*)rss_child)->getUrl()); + item->setData(2, Qt::DisplayRole, QVariant((int)rss_child->getType())); + } else { + item->setData(0,Qt::DecorationRole, QVariant(QIcon(QString::fromUtf8(":/Icons/oxygen/folder.png")))); + item->setData(1, Qt::DisplayRole, ((RssFolder*)rss_child)->getName()); + item->setData(2, Qt::DisplayRole, QVariant((int)rss_child->getType())); + // Recurvive call to load sub folders/files + fillFeedsList(item, (RssFolder*)rss_child); + } } } @@ -243,7 +274,7 @@ void RSSImp::refreshNewsList(QTreeWidgetItem* item) { listNews->clear(); return; } - RssStream *stream = rssmanager->getFeed(getCurrentFeedUrl()); + RssStream *stream = (RssStream*)rssmanager->getFile(getCurrentFeedPath()); qDebug("Getting the list of news"); QList news = stream->getNewsList(); // Clear the list first @@ -268,7 +299,8 @@ void RSSImp::refreshNewsList(QTreeWidgetItem* item) { // display a news void RSSImp::refreshTextBrowser(QListWidgetItem *item) { if(!item) return; - RssItem* article = rssmanager->getFeed(getCurrentFeedUrl())->getItem(listNews->row(item)); + RssStream *stream = (RssStream*)rssmanager->getFile(getCurrentFeedPath()); + RssItem* article = stream->getItem(listNews->row(item)); QString html; html += "
"; html += "
"+article->getTitle() + "
"; @@ -284,7 +316,7 @@ void RSSImp::refreshTextBrowser(QListWidgetItem *item) { article->setRead(); item->setData(Qt::ForegroundRole, QVariant(QColor("grey"))); item->setData(Qt::DecorationRole, QVariant(QIcon(":/Icons/sphere.png"))); - updateFeedNbNews(getCurrentFeedUrl()); + updateFeedNbNews(stream); } void RSSImp::saveSlidersPosition() { @@ -308,15 +340,9 @@ void RSSImp::restoreSlidersPosition() { } QTreeWidgetItem* RSSImp::getTreeItemFromUrl(QString url) const{ - unsigned int nbItems = listStreams->topLevelItemCount(); - for(unsigned int i = 0; itopLevelItem(i); - if(item->text(1) == url) - return item; - } - qDebug("Cannot find url %s in feeds list", (const char*)url.toLocal8Bit()); - Q_ASSERT(false); // Should never go through here - return (QTreeWidgetItem*)0; + QList items = listStreams->findItems(url, Qt::MatchExactly, 1); + Q_ASSERT(items.size() == 1); + return items.at(0); } void RSSImp::updateFeedIcon(QString url, QString icon_path){ @@ -324,10 +350,9 @@ void RSSImp::updateFeedIcon(QString url, QString icon_path){ item->setData(0,Qt::DecorationRole, QVariant(QIcon(icon_path))); } -void RSSImp::updateFeedNbNews(QString url){ - QTreeWidgetItem *item = getTreeItemFromUrl(url); - RssStream *stream = rssmanager->getFeed(url); - item->setText(0, stream->getAliasOrUrl() + QString::fromUtf8(" (") + QString::number(stream->getNbUnRead(), 10)+ QString(")")); +void RSSImp::updateFeedNbNews(RssStream* stream){ + QTreeWidgetItem *item = getTreeItemFromUrl(stream->getUrl()); + item->setText(0, stream->getName() + QString::fromUtf8(" (") + QString::number(stream->getNbUnRead(), 10)+ QString(")")); } QString RSSImp::getCurrentFeedUrl() const { @@ -339,10 +364,9 @@ QString RSSImp::getCurrentFeedUrl() const { void RSSImp::updateFeedInfos(QString url, QString aliasOrUrl, unsigned int nbUnread){ QTreeWidgetItem *item = getTreeItemFromUrl(url); - RssStream *stream = rssmanager->getFeed(url); + RssStream *stream = (RssStream*)rssmanager->getFile(getItemPath(item)); item->setText(0, aliasOrUrl + QString::fromUtf8(" (") + QString::number(nbUnread, 10)+ QString(")")); item->setData(0,Qt::DecorationRole, QVariant(QIcon(stream->getIconPath()))); - item->setToolTip(0, QString::fromUtf8("")+tr("Description:")+QString::fromUtf8(" ")+stream->getDescription()+QString::fromUtf8("
")+tr("url:")+QString::fromUtf8(" ")+stream->getUrl()+QString::fromUtf8("
")+tr("Last refresh:")+QString::fromUtf8(" ")+stream->getLastRefreshElapsedString()); // If the feed is selected, update the displayed news if(listStreams->currentItem() == item){ refreshNewsList(item); @@ -352,8 +376,9 @@ void RSSImp::updateFeedInfos(QString url, QString aliasOrUrl, unsigned int nbUnr RSSImp::RSSImp(bittorrent *BTSession) : QWidget(), BTSession(BTSession){ setupUi(this); - // Hide second column (url) + // Hide second column (url) and third column (type) listStreams->hideColumn(1); + listStreams->hideColumn(2); rssmanager = new RssManager(BTSession); fillFeedsList(); @@ -364,8 +389,8 @@ RSSImp::RSSImp(bittorrent *BTSession) : QWidget(), BTSession(BTSession){ connect(listNews, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(displayItemsListMenu(const QPoint&))); // Feeds list actions - connect(actionDelete_feed, SIGNAL(triggered()), this, SLOT(deleteSelectedFeeds())); - connect(actionRename_feed, SIGNAL(triggered()), this, SLOT(renameStream())); + connect(actionDelete_feed, SIGNAL(triggered()), this, SLOT(deleteSelectedItems())); + connect(actionRename_feed, SIGNAL(triggered()), this, SLOT(renameFiles())); connect(actionUpdate_feed, SIGNAL(triggered()), this, SLOT(refreshSelectedStreams())); connect(actionNew_subscription, SIGNAL(triggered()), this, SLOT(on_newFeedButton_clicked())); connect(actionUpdate_all_feeds, SIGNAL(triggered()), this, SLOT(on_updateAllButton_clicked())); @@ -379,9 +404,7 @@ RSSImp::RSSImp(bittorrent *BTSession) : QWidget(), BTSession(BTSession){ connect(listStreams, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), this, SLOT(refreshNewsList(QTreeWidgetItem*))); connect(listNews, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(refreshTextBrowser(QListWidgetItem *))); connect(listNews, SIGNAL(itemDoubleClicked(QListWidgetItem *)), this, SLOT(downloadTorrent())); - refreshTimeTimer = new QTimer(this); - connect(refreshTimeTimer, SIGNAL(timeout()), this, SLOT(updateLastRefreshedTimeForStreams())); - refreshTimeTimer->start(60000); // 1min + // Select first news of first feed selectFirstFeed(); // Refresh all feeds @@ -403,7 +426,6 @@ void RSSImp::selectFirstFeed(){ RSSImp::~RSSImp(){ qDebug("Deleting RSSImp..."); - delete refreshTimeTimer; delete rssmanager; qDebug("RSSImp deleted"); } diff --git a/src/rss_imp.h b/src/rss_imp.h index f41f3e222..b8673195f 100644 --- a/src/rss_imp.h +++ b/src/rss_imp.h @@ -33,9 +33,8 @@ #define REFRESH_MAX_LATENCY 600000 #include "ui_rss.h" +#include "rss.h" -class QTimer; -class RssManager; class bittorrent; class RSSImp : public QWidget, public Ui::RSS{ @@ -43,11 +42,10 @@ class RSSImp : public QWidget, public Ui::RSS{ private: RssManager *rssmanager; - QTimer *refreshTimeTimer; bittorrent *BTSession; public slots: - void deleteSelectedFeeds(); + void deleteSelectedItems(); protected slots: void on_newFeedButton_clicked(); @@ -55,19 +53,18 @@ protected slots: void on_markReadButton_clicked(); void displayRSSListMenu(const QPoint&); void displayItemsListMenu(const QPoint&); - void renameStream(); + void renameFiles(); void refreshSelectedStreams(); void copySelectedFeedsURL(); void refreshNewsList(QTreeWidgetItem* item); void refreshTextBrowser(QListWidgetItem *); - void updateLastRefreshedTimeForStreams(); void updateFeedIcon(QString url, QString icon_path); void updateFeedInfos(QString url, QString aliasOrUrl, unsigned int nbUnread); void openNewsUrl(); void downloadTorrent(); - void fillFeedsList(); + void fillFeedsList(QTreeWidgetItem *parent=0, RssFolder *rss_parent=0); void selectFirstFeed(); - void updateFeedNbNews(QString url); + void updateFeedNbNews(RssStream* stream); void saveSlidersPosition(); void restoreSlidersPosition(); void showFeedDownloader(); @@ -77,6 +74,10 @@ public: ~RSSImp(); QTreeWidgetItem* getTreeItemFromUrl(QString url) const; QString getCurrentFeedUrl() const; + QTreeWidgetItem* getItemFromPath(QStringList path) const; + QStringList getItemPath(QTreeWidgetItem *item) const; + QStringList getCurrentFeedPath() const; + RssFile::FileType getItemType(QTreeWidgetItem *item) const; }; #endif diff --git a/src/src.pro b/src/src.pro index 00e19da82..77d15238a 100644 --- a/src/src.pro +++ b/src/src.pro @@ -226,5 +226,6 @@ SOURCES += GUI.cpp \ httpresponsegenerator.cpp \ eventmanager.cpp \ SearchTab.cpp \ - ico.cpp + ico.cpp \ + rss.cpp DESTDIR = .