diff --git a/TODO b/TODO index 485a8e5fa..898674c5b 100644 --- a/TODO +++ b/TODO @@ -47,7 +47,8 @@ - make use of finishedChecking alert if hydri implements it - Clean up delayed progress column sorting code - Clean up pause after checking code - - use abort() for session deletion? + - add delete hotkey for rss feeds + - Test rss now that it has been rewritten - Wait for some bug fixes in libtorrent : - upload/download limit per torrent (Ticket #83) - double free or corruption on exit (Ticket #84) diff --git a/src/rss.h b/src/rss.h index 02cead3a7..775a31709 100644 --- a/src/rss.h +++ b/src/rss.h @@ -29,11 +29,6 @@ // avoid crash if too many refresh #define REFRESH_FREQ_MAX 5000 -// type of refresh -#define ICON 0 -#define NEWS 1 -#define LATENCY 2 - #include #include #include @@ -42,6 +37,7 @@ #include #include #include +#include #include "misc.h" #include "downloadThread.h" @@ -129,18 +125,15 @@ class RssStream : public QObject{ QString filePath; QString iconPath; QList listItem; - downloadThread* downloaderRss; - downloadThread* downloaderIcon; QTime lastRefresh; bool read; bool downloadFailure; - - signals: - void refreshFinished(QString msg, const unsigned short& type); + bool refreshed; + bool currently_loading; public slots : // read and store the downloaded rss' informations - void processDownloadedFile(QString, QString file_path) { + void processDownloadedFile(QString file_path) { // delete the former file if(QFile::exists(filePath)) { QFile::remove(filePath); @@ -148,38 +141,30 @@ class RssStream : public QObject{ filePath = file_path; downloadFailure = false; openRss(); - emit refreshFinished(url, NEWS); + lastRefresh.start(); + refreshed = true; } - // display the icon in the rss window - void displayIcon(QString, QString file_path) { - iconPath = file_path; - openIcon(); - emit refreshFinished(url, ICON); + void setDownloadFailed(){ + // Change the stream icon to a red cross + downloadFailure = true; + lastRefresh.start(); } public: RssStream(QString _url) { url = _url; - alias = url; - read = true; + alias = ""; + refreshed = false; downloadFailure = false; - downloaderRss = new downloadThread(this); - downloaderIcon = new downloadThread(this); - connect(downloaderRss, SIGNAL(downloadFinished(QString, QString)), this, SLOT(processDownloadedFile(QString, QString))); - connect(downloaderRss, SIGNAL(downloadFailure(QString, QString)), this, SLOT(handleDownloadFailure(QString, QString))); - downloaderRss->downloadUrl(url); + currently_loading = false; // XXX: remove it when gif can be displayed iconPath = ":/Icons/rss.png"; - getIcon(); - lastRefresh.start(); qDebug("RSSStream constructed"); } ~RssStream(){ - removeAllItem(); - delete downloaderRss; - delete downloaderIcon; + removeAllItems(); if(QFile::exists(filePath)) QFile::remove(filePath); if(QFile::exists(iconPath) && !iconPath.startsWith(":/")) @@ -187,16 +172,19 @@ class RssStream : public QObject{ } // delete all the items saved - void removeAllItem() { + void removeAllItems() { unsigned int listSize = listItem.size(); for(unsigned int i=0; idownloadUrl(url); - lastRefresh.start(); + void setLoading(bool val) { + currently_loading = val; + } + + bool isLoading() { + return currently_loading; } QString getTitle() const{ @@ -207,11 +195,20 @@ class RssStream : public QObject{ return alias; } - //prefer the RssManager::setAlias, do not save the changed ones + // Prefer the RssManager::setAlias to save the changed ones void setAlias(QString _alias){ alias = _alias; } + // Return the alias if the stream has one, the url if it has no alias + QString getAliasOrUrl() const{ + if(!alias.isEmpty()) + return alias; + if(!title.isEmpty()) + return title; + return url; + } + QString getLink() const{ return link; } @@ -233,31 +230,44 @@ class RssStream : public QObject{ } QString getIconPath() const{ + if(downloadFailure) + return ":/Icons/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 short getListSize() const{ + unsigned int getNbNews() const{ return listItem.size(); } - unsigned short getNbNonRead() const{ - int i=0, nbnonread=0; - for(i=0; iisRead()) - nbnonread++; + unsigned int getNbUnRead() const{ + unsigned int nbUnread=0; + RssItem *item; + foreach(item, listItem){ + if(!item->isRead()) + ++nbUnread; } - return nbnonread; + return nbUnread; } - QList getListItem() const{ + 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("<", "<")); } @@ -265,24 +275,10 @@ class RssStream : public QObject{ return lastRefresh.elapsed(); } - QString getLastRefresh() const{ - return QString::number(lastRefresh.hour())+"h"+QString::number(lastRefresh.minute())+"m"; - } - - bool isRead() const { - return read; - } - - void setRead() { - read = true; - } - // download the icon from the adress - void getIcon() { + QString getIconUrl() { QUrl siteUrl(url); - QString iconUrl = "http://"+siteUrl.host()+"/favicon.ico"; - connect(downloaderIcon, SIGNAL(downloadFinished(QString, QString)), this, SLOT(displayIcon(QString, QString))); - downloaderIcon->downloadUrl(iconUrl); + return QString("http://"+siteUrl.host()+"/favicon.ico"); } private: @@ -300,7 +296,7 @@ class RssStream : public QObject{ } QDomNode rss = root.firstChild(); QDomElement channel = root.firstChild().toElement(); - unsigned short listsize = getListSize(); + unsigned short listsize = getNbNews(); for(unsigned short i=0; igetTitle(); - unsigned short listsize = getListSize(); + unsigned short listsize = getNbNews(); for(unsigned short i=0; igetTitle() == firstTitle) lastindex = i; @@ -347,7 +342,7 @@ class RssStream : public QObject{ for(unsigned short i=0; iSTREAM_MAX_ITEM) { + while(getNbNews()>STREAM_MAX_ITEM) { listItem.removeAt(STREAM_MAX_ITEM); } @@ -380,31 +375,6 @@ class RssStream : public QObject{ } return return_lecture; } - - void openIcon() { - QImage fileIcon(iconPath,0); - if(!fileIcon.load(iconPath, 0)) { - qDebug("error: icon open failed, no file or empty file at "+iconPath.toUtf8()); - if(QFile::exists(iconPath)) { - QFile::remove(iconPath); - if(downloadFailure){ - iconPath = ":/Icons/unavailable.png"; - qDebug("Could not open favicon nor rss stream, setting icon to unavailable (%s)",(const char*)getAlias().toUtf8()); - } else { - iconPath = ":/Icons/rss.png"; - qDebug("Could not open favicon, setting default icon (%s)",(const char*)getAlias().toUtf8()); - } - } - } - } - - protected slots: - void handleDownloadFailure(QString, QString){ - // Change the stream icon to a red cross - iconPath = ":/Icons/unavailable.png"; - downloadFailure = true; - emit refreshFinished(url, ICON); - } }; // global class, manage the whole rss stream @@ -412,162 +382,225 @@ class RssManager : public QObject{ Q_OBJECT private : - QList streamList; - QStringList streamListUrl; + QHash streams; + downloadThread *downloader; signals: - void streamNeedRefresh(const unsigned short&, const unsigned short&); + void feedInfosChanged(QString url, QString aliasOrUrl, unsigned int nbUnread); + void feedIconChanged(QString url, QString icon_path); public slots : - void streamNeedRefresh(QString _url, const unsigned short& type) { - emit(streamNeedRefresh(hasStream(_url), type)); + + 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.toUtf8()); + } + 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()); + } + + 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.toUtf8(), (const char*)reason.toUtf8()); + 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.toUtf8(), (const char*)reason.toUtf8()); + stream->setDownloadFailed(); } public : RssManager(){ + 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(); } ~RssManager(){ - saveStreamList(); - unsigned int streamListSize = streamList.size(); - for(unsigned int i=0; istreamListAlias.size()) - streamListUrl.removeLast(); - while(streamListAlias.size()>streamListUrl.size()) - streamListAlias.removeLast(); - qDebug("NB RSS streams loaded: %d", streamListUrl.size()); - settings.endGroup(); - unsigned int streamListUrlSize = streamListUrl.size(); - for(unsigned int i=0; isetAlias(streamListAlias.at(i)); - streamList.append(stream); - connect(stream, SIGNAL(refreshFinished(QString, const unsigned short&)), this, SLOT(streamNeedRefresh(QString, const unsigned short&))); + 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; } - qDebug("Streams loaded"); + QString url; + unsigned int i = 0; + foreach(url, streamsUrl){ + RssStream *stream = new RssStream(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(){ - streamListUrl.clear(); - QStringList streamListAlias; - unsigned int nbStreams = getNbStreams(); - for(unsigned int i=0; igetUrl()); - streamListAlias.append(getStream(i)->getAlias()); + 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", streamListUrl); - settings.setValue("streamAlias", streamListAlias); + settings.setValue("streamList", streamsUrl); + settings.setValue("streamAlias", aliases); settings.endGroup(); } // add a stream to the manager void addStream(RssStream* stream){ - if(hasStream(stream) < 0){ - streamList.append(stream); - streamListUrl.append(stream->getUrl()); - connect(stream, SIGNAL(refreshFinished(QString, const unsigned short&)), this, SLOT(streamNeedRefresh(QString, const unsigned short&))); - }else{ + 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 - void addStream(QString url){ - if(hasStream(url) < 0) { - RssStream* stream = new RssStream(url); - streamList.append(stream); - streamListUrl.append(url); - connect(stream, SIGNAL(refreshFinished(QString, const unsigned short&)), this, SLOT(streamNeedRefresh(QString, const unsigned short&))); - }else { + 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(url); + streams[url] = stream; + refresh(url); + return stream; } // remove a stream from the manager void removeStream(RssStream* stream){ - short index = hasStream(stream); - if(index != -1){ - delete streamList.takeAt(index); - streamListUrl.removeAt(index); + 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(){ - QList newStreamList; - QStringList newUrlList; - unsigned int streamListSize = streamList.size(); - for(unsigned int i=0; irefresh(); - connect(getStream(i), SIGNAL(refreshFinished(QString, const unsigned short&)), this, SLOT(streamNeedRefresh(QString, const unsigned short&))); + 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.toUtf8()); + stream->setLoading(true); + downloader->downloadUrl(url); + if(!stream->hasCustomIcon()){ + downloader->downloadUrl(stream->getIconUrl()); + } } } - void refresh(unsigned int index) { - if(indexgetLastRefreshElapsed()>REFRESH_FREQ_MAX) { - getStream(index)->refresh(); - connect(getStream(index), SIGNAL(refreshFinished(QString, const unsigned short&)), this, SLOT(streamNeedRefresh(QString, const unsigned short&))); - } + void refresh(QString url) { + 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"); } } - // return the position index of a stream, if the manager owns it - short hasStream(RssStream* stream) const{ - return hasStream(stream->getUrl()); + // XXX: Used? + unsigned int getNbFeeds() { + return streams.size(); } - short hasStream(QString url) const{ - return streamListUrl.indexOf(url); + RssStream* getFeed(QString url){ + Q_ASSERT(streams.contains(url)); + return streams[url]; } - RssStream* getStream(const unsigned short& index) const{ - return streamList.at(index); + // 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()); } - unsigned int getNbStreams() { - return streamList.size(); - } - - //set an alias to an stream and save it for later - void setAlias(unsigned short index, QString newAlias) { - if(newAlias.length()>=2 && !getListAlias().contains(newAlias, Qt::CaseInsensitive)) { - getStream(index)->setAlias(newAlias); - } - } - - QStringList getListAlias() { - QStringList listAlias; - unsigned int nbStreams = getNbStreams(); - for(unsigned short i=0; igetAlias()); - } - return listAlias; + // Return all the rss feeds we have + QList getRssFeeds() const { + return streams.values(); } }; diff --git a/src/rss.ui b/src/rss.ui index c98b73197..cf09754d2 100644 --- a/src/rss.ui +++ b/src/rss.ui @@ -41,7 +41,7 @@ Qt::CustomContextMenu - 1 + 2 diff --git a/src/rss_imp.cpp b/src/rss_imp.cpp index c39f936de..eddd2f455 100644 --- a/src/rss_imp.cpp +++ b/src/rss_imp.cpp @@ -31,12 +31,13 @@ // display a right-click menu void RSSImp::displayRSSListMenu(const QPoint& pos){ - moveCurrentItem(); QMenu myFinishedListMenu(this); QTreeWidgetItem* item = listStreams->itemAt(pos); - if(item!=NULL) { + QList selectedItems = listStreams->selectedItems(); + if(item != 0) { myFinishedListMenu.addAction(actionDelete); - myFinishedListMenu.addAction(actionRename); + if(selectedItems.size() == 1) + myFinishedListMenu.addAction(actionRename); myFinishedListMenu.addAction(actionRefresh); }else{ myFinishedListMenu.addAction(actionCreate); @@ -52,19 +53,29 @@ // delete a stream by a button void RSSImp::on_delStream_button_clicked() { - if(getNumStreamSelected()<0 || rssmanager->getNbStreams()==0) { - qDebug("no stream selected"); - return; - }else { - int ok = QMessageBox::question(this, tr("Are you sure? -- qBittorrent"), tr("Are you sure you want to delete this stream from the list?"), - tr("&Yes"), tr("&No"), - QString(), 0, 1); - if(ok==0) { - textBrowser->clear(); - listNews->clear(); - rssmanager->removeStream(rssmanager->getStream(getNumStreamSelected())); - refreshStreamList(); - } + QList selectedItems = listStreams->selectedItems(); + QTreeWidgetItem *item; + 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?"), + tr("&Yes"), tr("&No"), + QString(), 0, 1); + if(!ret) { + QStringList urlsToDelete; + foreach(item, selectedItems){ + QString url = item->data(1, Qt::DisplayRole).toString(); + urlsToDelete << url; + } + QString url; + foreach(url, urlsToDelete){ + if(selectedFeedUrl == url){ + textBrowser->clear(); + listNews->clear(); + } + rssmanager->removeStream(url); + delete getTreeItemFromUrl(url); + } + if(urlsToDelete.size()) + rssmanager->saveStreamList(); } } @@ -73,205 +84,167 @@ refreshAllStreams(); } - // display the news of a stream when click on it - void RSSImp::on_listStreams_clicked() { - if(rssmanager->getNbStreams()>0) { - moveCurrentItem(); - rssmanager->getStream(getNumStreamSelected())->setRead(); - // update the color of the stream, is it old ? - updateStreamName(getNumStreamSelected(), LATENCY); - refreshNewsList(); - } - } - - // display the content of a new when clicked on it - void RSSImp::on_listNews_clicked() { - listNews->item(listNews->currentRow())->setData(Qt::ForegroundRole, QVariant(QColor("grey"))); - listNews->item(listNews->currentRow())->setData(Qt::DecorationRole, QVariant(QIcon(":/Icons/sphere.png"))); - refreshTextBrowser(); - } - // open the url of the news in a browser - void RSSImp::on_listNews_doubleClicked() { - if(getNumStreamSelected()>=0 && listNews->currentRow()>=0 && rssmanager->getStream(getNumStreamSelected())->getListSize()>0) { - RssItem* currentItem = rssmanager->getStream(getNumStreamSelected())->getItem(listNews->currentRow()); - if(currentItem->getLink()!=NULL && currentItem->getLink().length()>5) - QDesktopServices::openUrl(QUrl(currentItem->getLink())); - } + void RSSImp::openInBrowser(QListWidgetItem *item) { + RssItem* news = rssmanager->getFeed(selectedFeedUrl)->getItem(listNews->row(item)); + QString link = news->getLink(); + if(!link.isEmpty()) + QDesktopServices::openUrl(QUrl(link)); } - // move the current selection if it is not a toplevelitem (id : stream) - void RSSImp::moveCurrentItem() { - if(getNumStreamSelected()<0) { - int index = listStreams->indexOfTopLevelItem(listStreams->currentItem()->parent()); - if(index>=0) - listStreams->setCurrentItem(listStreams->topLevelItem(index)); - else - listStreams->setCurrentItem(listStreams->topLevelItem(0)); - } - } - - //right-clik on stream : delete it - void RSSImp::deleteStream() { - if(rssmanager->getNbStreams()==0) { - qDebug("no stream selected"); - return; - }else { - int ok = QMessageBox::question(this, tr("Are you sure? -- qBittorrent"), tr("Are you sure you want to delete this stream from the list ?"), tr("&Yes"), tr("&No"), QString(), 0, 1); - if(ok==0) { - moveCurrentItem(); - textBrowser->clear(); - listNews->clear(); - rssmanager->removeStream(rssmanager->getStream(getNumStreamSelected())); - refreshStreamList(); - } - } - } - - //right-clik on stream : give him an alias + //right-click on stream : give him an alias void RSSImp::renameStream() { - if(rssmanager->getNbStreams()==0) { - qDebug("no stream selected"); - return; - }else { - moveCurrentItem(); - bool ok; - short index = getNumStreamSelected(); - QString newAlias = QInputDialog::getText(this, tr("Please choose a new name for this stream"), tr("New stream name:"), QLineEdit::Normal, rssmanager->getStream(index)->getAlias(), &ok); - if(ok) { - rssmanager->setAlias(index, newAlias); - updateStreamName(index, NEWS); - } + 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); + if(ok) { + rssmanager->setAlias(url, newAlias); } - } - //right-clik on stream : refresh it - void RSSImp::refreshStream() { - if(rssmanager->getNbStreams()>0) { - moveCurrentItem(); - short index = getNumStreamSelected(); - textBrowser->clear(); - listNews->clear(); - listStreams->topLevelItem(index)->setData(0,Qt::DecorationRole, QVariant(QIcon(":/Icons/loading.png"))); - rssmanager->refresh(index); + //right-click on stream : refresh it + void RSSImp::refreshSelectedStreams() { + QList selectedItems = listStreams->selectedItems(); + QTreeWidgetItem* item; + foreach(item, selectedItems){ + QString url = item->text(1); + textBrowser->clear(); + listNews->clear(); + rssmanager->refresh(url); + item->setData(0,Qt::DecorationRole, QVariant(QIcon(":/Icons/loading.png"))); } - updateLastRefreshedTimeForStreams(); } //right-click somewhere, refresh all the streams void RSSImp::refreshAllStreams() { textBrowser->clear(); listNews->clear(); - unsigned short nbstream = rssmanager->getNbStreams(); - for(unsigned short i=0; itopLevelItemCount(); + for(unsigned int i=0; itopLevelItem(i)->setData(0,Qt::DecorationRole, QVariant(QIcon(":/Icons/loading.png"))); rssmanager->refreshAll(); updateLastRefreshedTimeForStreams(); } + 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(" (0)")); + item->setData(0,Qt::DecorationRole, QVariant(QIcon(":/Icons/loading.png"))); + item->setData(1, Qt::DisplayRole, stream->getUrl()); + item->setToolTip(0, QString("")+tr("Description:")+QString(" ")+stream->getDescription()+QString("
")+tr("url:")+QString(" ")+stream->getUrl()+QString("
")+tr("Last refresh:")+QString(" ")+stream->getLastRefreshElapsedString()); + } + } + //right-click, register a new stream void RSSImp::createStream() { bool ok; QString newUrl = QInputDialog::getText(this, tr("Please type a rss stream url"), tr("Stream URL:"), QLineEdit::Normal, "http://", &ok); if(ok) { newUrl = newUrl.trimmed(); - if(!newUrl.isEmpty() && newUrl != "http://"){ - rssmanager->addStream(newUrl); - refreshStreamList(); + if(!newUrl.isEmpty()){ + RssStream *stream = rssmanager->addStream(newUrl); + if(stream == 0){ + // Already existing + QMessageBox::warning(this, tr("qBittorrent"), + tr("This rss feed is already in the list."), + QMessageBox::Ok); + return; + } + QTreeWidgetItem* item = new QTreeWidgetItem(listStreams); + item->setText(0, stream->getAliasOrUrl() + QString(" (0)")); + item->setText(1, stream->getUrl()); + item->setData(0,Qt::DecorationRole, QVariant(QIcon(":/Icons/loading.png"))); + item->setToolTip(0, QString("")+tr("Description:")+QString(" ")+stream->getDescription()+QString("
")+tr("url:")+QString(" ")+stream->getUrl()+QString("
")+tr("Last refresh:")+QString(" ")+stream->getLastRefreshElapsedString()); + if(listStreams->topLevelItemCount() == 1) + selectFirstFeed(); + rssmanager->refresh(newUrl); + rssmanager->saveStreamList(); } } } - // fills the streamList - void RSSImp::refreshStreamList() { - qDebug("Refreshing stream list"); - unsigned short nbstream = rssmanager->getNbStreams(); - listStreams->clear(); - QList streams; - for(unsigned short i=0; isetToolTip(0, QString("")+tr("Description:")+QString(" ")+rssmanager->getStream(i)->getDescription()+QString("
")+tr("url:")+QString(" ")+rssmanager->getStream(i)->getUrl()+QString("
")+tr("Last refresh:")+QString(" ")+rssmanager->getStream(i)->getLastRefreshElapsedString()); + void RSSImp::updateLastRefreshedTimeForStreams() { + qDebug("Refreshing last refresh time in rss list"); + 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("")+tr("Description:")+QString(" ")+stream->getDescription()+QString("
")+tr("url:")+QString(" ")+stream->getUrl()+QString("
")+tr("Last refresh:")+QString(" ")+stream->getLastRefreshElapsedString()); } - qDebug("Stream list refreshed"); } // fills the newsList - void RSSImp::refreshNewsList() { - if(rssmanager->getNbStreams()>0) { - RssStream* currentstream = rssmanager->getStream(getNumStreamSelected()); - listNews->clear(); - unsigned short currentStreamSize = currentstream->getListSize(); - for(unsigned short i=0; igetItem(i)->getTitle(), listNews); - if(currentstream->getItem(i)->isRead()){ - listNews->item(i)->setData(Qt::ForegroundRole, QVariant(QColor("grey"))); - listNews->item(i)->setData(Qt::DecorationRole, QVariant(QIcon(":/Icons/sphere.png"))); - } else { - listNews->item(i)->setData(Qt::DecorationRole, QVariant(QIcon(":/Icons/sphere2.png"))); - listNews->item(i)->setData(Qt::ForegroundRole, QVariant(QColor("blue"))); - } - if(i%2==0) - listNews->item(i)->setData(Qt::BackgroundRole, QVariant(QColor(0, 255, 255, 20))); - } + void RSSImp::refreshNewsList(QTreeWidgetItem* item, int) { + selectedFeedUrl = item->text(1); + RssStream *stream = rssmanager->getFeed(selectedFeedUrl); + listNews->clear(); + qDebug("Getting the list of news"); + QList news = stream->getNewsList(); + qDebug("Got the list of news"); + RssItem* article; + foreach(article, news){ + QListWidgetItem* it = new QListWidgetItem(article->getTitle(), listNews); + if(article->isRead()){ + it->setData(Qt::ForegroundRole, QVariant(QColor("grey"))); + it->setData(Qt::DecorationRole, QVariant(QIcon(":/Icons/sphere.png"))); + }else{ + it->setData(Qt::ForegroundRole, QVariant(QColor("blue"))); + it->setData(Qt::DecorationRole, QVariant(QIcon(":/Icons/sphere2.png"))); + } } + qDebug("Added all news to the GUI"); + selectFirstNews(); + qDebug("First news selected"); } // display a news - void RSSImp::refreshTextBrowser() { - if(getNumStreamSelected()>=0 && listNews->currentRow()>=0) { - RssItem* currentitem = rssmanager->getStream(getNumStreamSelected())->getItem(listNews->currentRow()); - textBrowser->setHtml(currentitem->getTitle()+" : \n"+currentitem->getDescription()); - currentitem->setRead(); - } + void RSSImp::refreshTextBrowser(QListWidgetItem *item) { + RssItem* article = rssmanager->getFeed(selectedFeedUrl)->getItem(listNews->row(item)); + textBrowser->setHtml(article->getTitle()+":
"+article->getDescription()); + article->setRead(); + item->setData(Qt::ForegroundRole, QVariant(QColor("grey"))); + item->setData(Qt::DecorationRole, QVariant(QIcon(":/Icons/sphere.png"))); + updateFeedNbNews(selectedFeedUrl); } - void RSSImp::updateLastRefreshedTimeForStreams() { - unsigned int nbStreams = rssmanager->getNbStreams(); - for(unsigned int i=0; itopLevelItem(i)->setToolTip(0, QString("")+tr("Description:")+QString(" ")+rssmanager->getStream(i)->getDescription()+QString("
")+tr("url:")+QString(" ")+rssmanager->getStream(i)->getUrl()+QString("
")+tr("Last refresh:")+QString(" ")+rssmanager->getStream(i)->getLastRefreshElapsedString()); + 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.toUtf8()); + Q_ASSERT(false); // Should never go through here + return (QTreeWidgetItem*)0; } - // show the number of news for a stream, his status and an icon - void RSSImp::updateStreamName(const unsigned short& i, const unsigned short& type) { - // icon has just been download - if(type == ICON) { - listStreams->topLevelItem(i)->setData(0,Qt::DecorationRole, QVariant(QIcon(rssmanager->getStream(i)->getIconPath()))); - } - // on click, show the age of the stream - if(type == LATENCY) { - unsigned short nbitem = rssmanager->getStream(i)->getNbNonRead(); - listStreams->topLevelItem(i)->setText(0,rssmanager->getStream(i)->getAlias().toUtf8()+" ("+QString::number(nbitem,10).toUtf8()+")"); - if(nbitem==0) - listStreams->topLevelItem(i)->setData(0,Qt::ForegroundRole, QVariant(QColor("red"))); - else if(rssmanager->getStream(i)->getLastRefreshElapsed()>REFRESH_MAX_LATENCY) - listStreams->topLevelItem(i)->setData(0,Qt::ForegroundRole, QVariant(QColor("orange"))); - else - listStreams->topLevelItem(i)->setData(0,Qt::ForegroundRole, QVariant(QColor("green"))); - listStreams->topLevelItem(getNumStreamSelected())->setData(0,Qt::BackgroundRole, QVariant(QColor("white"))); - } - // when news are refreshed, update all informations - if(type == NEWS) { - unsigned short nbitem = rssmanager->getStream(i)->getListSize(); - listStreams->topLevelItem(i)->setText(0,rssmanager->getStream(i)->getAlias().toUtf8()+" ("+QString::number(nbitem,10).toUtf8()+")"); - if(nbitem==0) - listStreams->topLevelItem(i)->setData(0,Qt::ForegroundRole, QVariant(QColor("red"))); - else if(rssmanager->getStream(i)->getLastRefreshElapsed()>REFRESH_MAX_LATENCY) - listStreams->topLevelItem(i)->setData(0,Qt::ForegroundRole, QVariant(QColor("orange"))); - else - listStreams->topLevelItem(i)->setData(0,Qt::ForegroundRole, QVariant(QColor("green"))); + void RSSImp::updateFeedIcon(QString url, QString icon_path){ + QTreeWidgetItem *item = getTreeItemFromUrl(url); + item->setData(0,Qt::DecorationRole, QVariant(QIcon(icon_path))); + } - if(!rssmanager->getStream(i)->isRead()) - listStreams->topLevelItem(i)->setData(0,Qt::BackgroundRole, QVariant(QColor(0, 255, 0, 20))); - if(getNumStreamSelected()==i) { - listNews->clear(); - refreshNewsList(); - } - listStreams->topLevelItem(i)->setData(0,Qt::DecorationRole, QVariant(QIcon(rssmanager->getStream(i)->getIconPath()))); - // update description and display last refresh - listStreams->topLevelItem(i)->setToolTip(0, QString("")+tr("Description:")+QString(" ")+rssmanager->getStream(i)->getDescription()+QString("
")+tr("url:")+QString(" ")+rssmanager->getStream(i)->getUrl()+QString("
")+tr("Last refresh:")+QString(" ")+rssmanager->getStream(i)->getLastRefreshElapsedString()); + void RSSImp::updateFeedNbNews(QString url){ + QTreeWidgetItem *item = getTreeItemFromUrl(url); + RssStream *stream = rssmanager->getFeed(url); + item->setText(0, stream->getAliasOrUrl() + QString(" (") + QString::number(stream->getNbUnRead(), 10)+ String(")")); + } + + void RSSImp::updateFeedInfos(QString url, QString aliasOrUrl, unsigned int nbUnread){ + QTreeWidgetItem *item = getTreeItemFromUrl(url); + RssStream *stream = rssmanager->getFeed(url); + item->setText(0, aliasOrUrl + QString(" (") + QString::number(nbUnread, 10)+ String(")")); + item->setData(0,Qt::DecorationRole, QVariant(QIcon(stream->getIconPath()))); + item->setToolTip(0, QString("")+tr("Description:")+QString(" ")+stream->getDescription()+QString("
")+tr("url:")+QString(" ")+stream->getUrl()+QString("
")+tr("Last refresh:")+QString(" ")+stream->getLastRefreshElapsedString()); + // If the feed is selected, update the displayed news + if(selectedFeedUrl == url){ + refreshNewsList(getTreeItemFromUrl(url), 0); } } @@ -288,29 +261,50 @@ actionCreate->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/add.png"))); actionRefreshAll->setIcon(QIcon(QString::fromUtf8(":/Icons/refresh.png"))); + // Hide second column (url) + listStreams->hideColumn(1); + rssmanager = new RssManager(); - connect(rssmanager, SIGNAL(streamNeedRefresh(const unsigned short&, const unsigned short&)), this, SLOT(updateStreamName(const unsigned short&, const unsigned short&))); + fillFeedsList(); + connect(rssmanager, SIGNAL(feedInfosChanged(QString, QString, unsigned int)), this, SLOT(updateFeedInfos(QString, QString, unsigned int))); + connect(rssmanager, SIGNAL(feedIconChanged(QString, QString)), this, SLOT(updateFeedIcon(QString, QString))); connect(listStreams, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(displayRSSListMenu(const QPoint&))); - connect(actionDelete, SIGNAL(triggered()), this, SLOT(deleteStream())); + connect(actionDelete, SIGNAL(triggered()), this, SLOT(on_delStream_button_clicked())); connect(actionRename, SIGNAL(triggered()), this, SLOT(renameStream())); - connect(actionRefresh, SIGNAL(triggered()), this, SLOT(refreshStream())); + connect(actionRefresh, SIGNAL(triggered()), this, SLOT(refreshSelectedStreams())); connect(actionCreate, SIGNAL(triggered()), this, SLOT(createStream())); connect(actionRefreshAll, SIGNAL(triggered()), this, SLOT(refreshAllStreams())); + connect(listStreams, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(refreshNewsList(QTreeWidgetItem*,int))); + connect(listNews, SIGNAL(itemClicked(QListWidgetItem *)), this, SLOT(refreshTextBrowser(QListWidgetItem *))); + connect(listNews, SIGNAL(itemDoubleClicked(QListWidgetItem *)), this, SLOT(openInBrowser(QListWidgetItem *))); refreshTimeTimer = new QTimer(this); connect(refreshTimeTimer, SIGNAL(timeout()), this, SLOT(updateLastRefreshedTimeForStreams())); refreshTimeTimer->start(60000); // 1min - refreshStreamList(); - refreshTextBrowser(); + // Select first news of first feed + selectFirstFeed(); + // Refresh all feeds + rssmanager->refreshAll(); qDebug("RSSImp constructed"); } + void RSSImp::selectFirstFeed(){ + if(listStreams->topLevelItemCount()){ + QTreeWidgetItem *first = listStreams->topLevelItem(0); + listStreams->setCurrentItem(first); + selectedFeedUrl = first->text(1); + } + } + + void RSSImp::selectFirstNews(){ + if(listNews->count()){ + listNews->setCurrentRow(0); + refreshTextBrowser(listNews->currentItem()); + } + } + RSSImp::~RSSImp(){ delete refreshTimeTimer; delete rssmanager; } - short RSSImp::getNumStreamSelected(){ - return listStreams->indexOfTopLevelItem(listStreams->currentItem()); - } - diff --git a/src/rss_imp.h b/src/rss_imp.h index fe9cbd945..dff1d6bc6 100644 --- a/src/rss_imp.h +++ b/src/rss_imp.h @@ -25,10 +25,6 @@ #include "ui_rss.h" -#define DESCRIPTION_CHILD 0 -#define URL_CHILD 1 -#define TIME_CHILD 2 - class QTimer; class RssManager; @@ -38,31 +34,32 @@ class RSSImp : public QWidget, public Ui::RSS{ private: RssManager *rssmanager; QTimer *refreshTimeTimer; + QString selectedFeedUrl; protected slots: void on_addStream_button_clicked(); void on_delStream_button_clicked(); void on_refreshAll_button_clicked(); - void on_listStreams_clicked(); - void on_listNews_clicked(); - void on_listNews_doubleClicked(); void displayRSSListMenu(const QPoint&); - void moveCurrentItem(); - void deleteStream(); void renameStream(); - void refreshStream(); + void refreshSelectedStreams(); void createStream(); - void updateStreamName(const unsigned short&, const unsigned short&); void refreshAllStreams(); - void refreshStreamList(); - void refreshNewsList(); - void refreshTextBrowser(); - short getNumStreamSelected(); + void refreshNewsList(QTreeWidgetItem* item, int); + void refreshTextBrowser(QListWidgetItem *); void updateLastRefreshedTimeForStreams(); + void updateFeedIcon(QString url, QString icon_path); + void updateFeedInfos(QString url, QString aliasOrUrl, unsigned int nbUnread); + void openInBrowser(QListWidgetItem *); + void fillFeedsList(); + void selectFirstFeed(); + void selectFirstNews(); + void updateFeedNbNews(QString url); public: RSSImp(); ~RSSImp(); + QTreeWidgetItem* getTreeItemFromUrl(QString url) const; }; #endif