summaryrefslogtreecommitdiffstats
path: root/akregator/src/feed.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'akregator/src/feed.cpp')
-rw-r--r--akregator/src/feed.cpp857
1 files changed, 857 insertions, 0 deletions
diff --git a/akregator/src/feed.cpp b/akregator/src/feed.cpp
new file mode 100644
index 000000000..c088654a2
--- /dev/null
+++ b/akregator/src/feed.cpp
@@ -0,0 +1,857 @@
+/*
+ This file is part of Akregator.
+
+ Copyright (C) 2004 Stanislav Karchebny <Stanislav.Karchebny@kdemail.net>
+ 2005 Frank Osterfeld <frank.osterfeld at kdemail.net>
+
+ 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.
+
+ As a special exception, permission is given to link this program
+ with any edition of Qt, and distribute the resulting executable,
+ without including the source code for Qt in the source distribution.
+*/
+
+#include <qtimer.h>
+#include <qdatetime.h>
+#include <qlistview.h>
+#include <qdom.h>
+#include <qmap.h>
+#include <qpixmap.h>
+#include <qvaluelist.h>
+
+#include <kurl.h>
+#include <kdebug.h>
+#include <kglobal.h>
+#include <kstandarddirs.h>
+
+#include "akregatorconfig.h"
+#include "article.h"
+#include "articleinterceptor.h"
+#include "feed.h"
+#include "folder.h"
+#include "fetchqueue.h"
+#include "feediconmanager.h"
+#include "feedstorage.h"
+#include "storage.h"
+#include "treenodevisitor.h"
+#include "utils.h"
+
+#include "librss/librss.h"
+
+namespace Akregator {
+
+class Feed::FeedPrivate
+{
+ public:
+ bool autoFetch;
+ int fetchInterval;
+ ArchiveMode archiveMode;
+ int maxArticleAge;
+ int maxArticleNumber;
+ bool markImmediatelyAsRead;
+ bool useNotification;
+ bool loadLinkedWebsite;
+
+ bool fetchError;
+
+ int lastErrorFetch; // save time of last fetch that went wrong.
+ // != lastFetch property from the archive
+ // (that saves the last _successfull fetch!)
+ // workaround for 3.5.x
+
+ int fetchTries;
+ bool followDiscovery;
+ RSS::Loader* loader;
+ bool articlesLoaded;
+ Backend::FeedStorage* archive;
+
+ QString xmlUrl;
+ QString htmlUrl;
+ QString description;
+
+ /** list of feed articles */
+ QMap<QString, Article> articles;
+
+ /** caches guids of tagged articles. key: tag, value: list of guids */
+ QMap<QString, QStringList> taggedArticles;
+
+ /** list of deleted articles. This contains **/
+ QValueList<Article> deletedArticles;
+
+ /** caches guids of deleted articles for notification */
+
+ QValueList<Article> addedArticlesNotify;
+ QValueList<Article> removedArticlesNotify;
+ QValueList<Article> updatedArticlesNotify;
+
+ QPixmap imagePixmap;
+ RSS::Image image;
+ QPixmap favicon;
+};
+
+QString Feed::archiveModeToString(ArchiveMode mode)
+{
+ switch (mode)
+ {
+ case keepAllArticles:
+ return "keepAllArticles";
+ case disableArchiving:
+ return "disableArchiving";
+ case limitArticleNumber:
+ return "limitArticleNumber";
+ case limitArticleAge:
+ return "limitArticleAge";
+ default:
+ return "globalDefault";
+ }
+
+ // in a perfect world, this is never reached
+
+ return "globalDefault";
+}
+
+Feed* Feed::fromOPML(QDomElement e)
+{
+
+ Feed* feed = 0;
+
+ if( e.hasAttribute("xmlUrl") || e.hasAttribute("xmlurl") || e.hasAttribute("xmlURL") )
+ {
+ QString title = e.hasAttribute("text") ? e.attribute("text") : e.attribute("title");
+
+ QString xmlUrl = e.hasAttribute("xmlUrl") ? e.attribute("xmlUrl") : e.attribute("xmlurl");
+ if (xmlUrl.isEmpty())
+ xmlUrl = e.attribute("xmlURL");
+
+ bool useCustomFetchInterval = e.attribute("useCustomFetchInterval") == "true" || e.attribute("autoFetch") == "true";
+ // "autoFetch" is used in 3.4
+ // Will be removed in KDE4
+
+ QString htmlUrl = e.attribute("htmlUrl");
+ QString description = e.attribute("description");
+ int fetchInterval = e.attribute("fetchInterval").toInt();
+ ArchiveMode archiveMode = stringToArchiveMode(e.attribute("archiveMode"));
+ int maxArticleAge = e.attribute("maxArticleAge").toUInt();
+ int maxArticleNumber = e.attribute("maxArticleNumber").toUInt();
+ bool markImmediatelyAsRead = e.attribute("markImmediatelyAsRead") == "true";
+ bool useNotification = e.attribute("useNotification") == "true";
+ bool loadLinkedWebsite = e.attribute("loadLinkedWebsite") == "true";
+ uint id = e.attribute("id").toUInt();
+
+ feed = new Feed();
+ feed->setTitle(title);
+ feed->setXmlUrl(xmlUrl);
+ feed->setCustomFetchIntervalEnabled(useCustomFetchInterval);
+ feed->setHtmlUrl(htmlUrl);
+ feed->setId(id);
+ feed->setDescription(description);
+ feed->setArchiveMode(archiveMode);
+ feed->setUseNotification(useNotification);
+ feed->setFetchInterval(fetchInterval);
+ feed->setMaxArticleAge(maxArticleAge);
+ feed->setMaxArticleNumber(maxArticleNumber);
+ feed->setMarkImmediatelyAsRead(markImmediatelyAsRead);
+ feed->setLoadLinkedWebsite(loadLinkedWebsite);
+ feed->loadArticles(); // TODO: make me fly: make this delayed
+ feed->loadImage();
+ }
+
+ return feed;
+}
+
+bool Feed::accept(TreeNodeVisitor* visitor)
+{
+ if (visitor->visitFeed(this))
+ return true;
+ else
+ return visitor->visitTreeNode(this);
+}
+
+QStringList Feed::tags() const
+{
+ return d->archive->tags();
+}
+
+Article Feed::findArticle(const QString& guid) const
+{
+ return d->articles[guid];
+}
+
+QValueList<Article> Feed::articles(const QString& tag)
+{
+ if (!d->articlesLoaded)
+ loadArticles();
+ if (tag.isNull())
+ return d->articles.values();
+ else
+ {
+ QValueList<Article> tagged;
+ QStringList guids = d->archive->articles(tag);
+ for (QStringList::ConstIterator it = guids.begin(); it != guids.end(); ++it)
+ tagged += d->articles[*it];
+ return tagged;
+
+ }
+}
+
+void Feed::loadImage()
+{
+ QString imageFileName = KGlobal::dirs()->saveLocation("cache", "akregator/Media/")
+ + Utils::fileNameForUrl(d->xmlUrl) +
+".png";
+ d->imagePixmap.load(imageFileName, "PNG");
+}
+
+void Feed::loadArticles()
+{
+ if (d->articlesLoaded)
+ return;
+
+ if (!d->archive)
+ d->archive = Backend::Storage::getInstance()->archiveFor(xmlUrl());
+
+ QStringList list = d->archive->articles();
+ for ( QStringList::ConstIterator it = list.begin(); it != list.end(); ++it)
+ {
+ Article mya(*it, this);
+ d->articles[mya.guid()] = mya;
+ if (mya.isDeleted())
+ d->deletedArticles.append(mya);
+ }
+
+ d->articlesLoaded = true;
+ enforceLimitArticleNumber();
+ recalcUnreadCount();
+}
+
+void Feed::recalcUnreadCount()
+{
+ QValueList<Article> tarticles = articles();
+ QValueList<Article>::Iterator it;
+ QValueList<Article>::Iterator en = tarticles.end();
+
+ int oldUnread = d->archive->unread();
+
+ int unread = 0;
+
+ for (it = tarticles.begin(); it != en; ++it)
+ if (!(*it).isDeleted() && (*it).status() != Article::Read)
+ ++unread;
+
+ if (unread != oldUnread)
+ {
+ d->archive->setUnread(unread);
+ nodeModified();
+ }
+}
+
+Feed::ArchiveMode Feed::stringToArchiveMode(const QString& str)
+{
+ if (str == "globalDefault")
+ return globalDefault;
+ if (str == "keepAllArticles")
+ return keepAllArticles;
+ if (str == "disableArchiving")
+ return disableArchiving;
+ if (str == "limitArticleNumber")
+ return limitArticleNumber;
+ if (str == "limitArticleAge")
+ return limitArticleAge;
+
+ return globalDefault;
+}
+
+Feed::Feed() : TreeNode(), d(new FeedPrivate)
+{
+ d->autoFetch = false;
+ d->fetchInterval = 30;
+ d->archiveMode = globalDefault;
+ d->maxArticleAge = 60;
+ d->maxArticleNumber = 1000;
+ d->markImmediatelyAsRead = false;
+ d->useNotification = false;
+ d->fetchError = false;
+ d->lastErrorFetch = 0;
+ d->fetchTries = 0;
+ d->loader = 0;
+ d->articlesLoaded = false;
+ d->archive = 0;
+ d->loadLinkedWebsite = false;
+}
+
+Feed::~Feed()
+{
+ slotAbortFetch();
+ emitSignalDestroyed();
+ delete d;
+ d = 0;
+}
+
+bool Feed::useCustomFetchInterval() const { return d->autoFetch; }
+
+void Feed::setCustomFetchIntervalEnabled(bool enabled) { d->autoFetch = enabled; }
+
+int Feed::fetchInterval() const { return d->fetchInterval; }
+
+void Feed::setFetchInterval(int interval) { d->fetchInterval = interval; }
+
+int Feed::maxArticleAge() const { return d->maxArticleAge; }
+
+void Feed::setMaxArticleAge(int maxArticleAge) { d->maxArticleAge = maxArticleAge; }
+
+int Feed::maxArticleNumber() const { return d->maxArticleNumber; }
+
+void Feed::setMaxArticleNumber(int maxArticleNumber) { d->maxArticleNumber = maxArticleNumber; }
+
+bool Feed::markImmediatelyAsRead() const { return d->markImmediatelyAsRead; }
+
+void Feed::setMarkImmediatelyAsRead(bool enabled)
+{
+ d->markImmediatelyAsRead = enabled;
+ if (enabled)
+ slotMarkAllArticlesAsRead();
+}
+
+void Feed::setUseNotification(bool enabled)
+{
+ d->useNotification = enabled;
+}
+
+bool Feed::useNotification() const
+{
+ return d->useNotification;
+}
+
+void Feed::setLoadLinkedWebsite(bool enabled)
+{
+ d->loadLinkedWebsite = enabled;
+}
+
+bool Feed::loadLinkedWebsite() const
+{
+ return d->loadLinkedWebsite;
+}
+
+const QPixmap& Feed::favicon() const { return d->favicon; }
+
+const QPixmap& Feed::image() const { return d->imagePixmap; }
+
+const QString& Feed::xmlUrl() const { return d->xmlUrl; }
+
+void Feed::setXmlUrl(const QString& s) { d->xmlUrl = s; }
+
+const QString& Feed::htmlUrl() const { return d->htmlUrl; }
+
+void Feed::setHtmlUrl(const QString& s) { d->htmlUrl = s; }
+
+const QString& Feed::description() const { return d->description; }
+
+void Feed::setDescription(const QString& s) { d->description = s; }
+
+bool Feed::fetchErrorOccurred() { return d->fetchError; }
+
+bool Feed::isArticlesLoaded() const { return d->articlesLoaded; }
+
+
+QDomElement Feed::toOPML( QDomElement parent, QDomDocument document ) const
+{
+ QDomElement el = document.createElement( "outline" );
+ el.setAttribute( "text", title() );
+ el.setAttribute( "title", title() );
+ el.setAttribute( "xmlUrl", d->xmlUrl );
+ el.setAttribute( "htmlUrl", d->htmlUrl );
+ el.setAttribute( "id", QString::number(id()) );
+ el.setAttribute( "description", d->description );
+ el.setAttribute( "useCustomFetchInterval", (useCustomFetchInterval() ? "true" : "false") );
+ el.setAttribute( "fetchInterval", QString::number(fetchInterval()) );
+ el.setAttribute( "archiveMode", archiveModeToString(d->archiveMode) );
+ el.setAttribute( "maxArticleAge", d->maxArticleAge );
+ el.setAttribute( "maxArticleNumber", d->maxArticleNumber );
+ if (d->markImmediatelyAsRead)
+ el.setAttribute( "markImmediatelyAsRead", "true" );
+ if (d->useNotification)
+ el.setAttribute( "useNotification", "true" );
+ if (d->loadLinkedWebsite)
+ el.setAttribute( "loadLinkedWebsite", "true" );
+ el.setAttribute( "maxArticleNumber", d->maxArticleNumber );
+ el.setAttribute( "type", "rss" ); // despite some additional fields, its still "rss" OPML
+ el.setAttribute( "version", "RSS" );
+ parent.appendChild( el );
+ return el;
+}
+
+void Feed::slotMarkAllArticlesAsRead()
+{
+ if (unread() > 0)
+ {
+ setNotificationMode(false, true);
+ QValueList<Article> tarticles = articles();
+ QValueList<Article>::Iterator it;
+ QValueList<Article>::Iterator en = tarticles.end();
+
+ for (it = tarticles.begin(); it != en; ++it)
+ (*it).setStatus(Article::Read);
+ setNotificationMode(true, true);
+ }
+}
+void Feed::slotAddToFetchQueue(FetchQueue* queue, bool intervalFetchOnly)
+{
+ if (!intervalFetchOnly)
+ queue->addFeed(this);
+ else
+ {
+ uint now = QDateTime::currentDateTime().toTime_t();
+
+ // workaround for 3.5.x: if the last fetch went wrong, try again after 30 minutes
+ // this fixes annoying behaviour of akregator, especially when the host is reachable
+ // but Akregator can't parse the feed (the host is hammered every minute then)
+ if ( fetchErrorOccurred() && now - d->lastErrorFetch <= 30*60 )
+ return;
+
+ int interval = -1;
+
+ if (useCustomFetchInterval() )
+ interval = fetchInterval() * 60;
+ else
+ if ( Settings::useIntervalFetch() )
+ interval = Settings::autoFetchInterval() * 60;
+
+ uint lastFetch = d->archive->lastFetch();
+
+ if ( interval > 0 && now - lastFetch >= (uint)interval )
+ queue->addFeed(this);
+ }
+}
+
+
+void Feed::appendArticles(const RSS::Document &doc)
+{
+ bool changed = false;
+
+ RSS::Article::List d_articles = doc.articles();
+ RSS::Article::List::ConstIterator it;
+ RSS::Article::List::ConstIterator en = d_articles.end();
+
+ int nudge=0;
+
+ QValueList<Article> deletedArticles = d->deletedArticles;
+
+ for (it = d_articles.begin(); it != en; ++it)
+ {
+ if ( !d->articles.contains((*it).guid()) ) // article not in list
+ {
+ Article mya(*it, this);
+ mya.offsetPubDate(nudge);
+ nudge--;
+ appendArticle(mya);
+
+ QValueList<ArticleInterceptor*> interceptors = ArticleInterceptorManager::self()->interceptors();
+ for (QValueList<ArticleInterceptor*>::ConstIterator it = interceptors.begin(); it != interceptors.end(); ++it)
+ (*it)->processArticle(mya);
+
+ d->addedArticlesNotify.append(mya);
+
+ if (!mya.isDeleted() && !markImmediatelyAsRead())
+ mya.setStatus(Article::New);
+ else
+ mya.setStatus(Article::Read);
+
+ changed = true;
+ }
+ else // article is in list
+ {
+ // if the article's guid is no hash but an ID, we have to check if the article was updated. That's done by comparing the hash values.
+ Article old = d->articles[(*it).guid()];
+ Article mya(*it, this);
+ if (!mya.guidIsHash() && mya.hash() != old.hash() && !old.isDeleted())
+ {
+ mya.setKeep(old.keep());
+ int oldstatus = old.status();
+ old.setStatus(Article::Read);
+
+ d->articles.remove(old.guid());
+ appendArticle(mya);
+
+ mya.setStatus(oldstatus);
+
+ d->updatedArticlesNotify.append(mya);
+ changed = true;
+ }
+ else if (old.isDeleted())
+ deletedArticles.remove(mya);
+ }
+ }
+
+ QValueList<Article>::ConstIterator dit = deletedArticles.begin();
+ QValueList<Article>::ConstIterator dtmp;
+ QValueList<Article>::ConstIterator den = deletedArticles.end();
+
+ // delete articles with delete flag set completely from archive, which aren't in the current feed source anymore
+ while (dit != den)
+ {
+ dtmp = dit;
+ ++dit;
+ d->articles.remove((*dtmp).guid());
+ d->archive->deleteArticle((*dtmp).guid());
+ d->deletedArticles.remove(*dtmp);
+ }
+
+ if (changed)
+ articlesModified();
+}
+
+bool Feed::usesExpiryByAge() const
+{
+ return ( d->archiveMode == globalDefault && Settings::archiveMode() == Settings::EnumArchiveMode::limitArticleAge) || d->archiveMode == limitArticleAge;
+}
+
+bool Feed::isExpired(const Article& a) const
+{
+ QDateTime now = QDateTime::currentDateTime();
+ int expiryAge = -1;
+// check whether the feed uses the global default and the default is limitArticleAge
+ if ( d->archiveMode == globalDefault && Settings::archiveMode() == Settings::EnumArchiveMode::limitArticleAge)
+ expiryAge = Settings::maxArticleAge() *24*3600;
+ else // otherwise check if this feed has limitArticleAge set
+ if ( d->archiveMode == limitArticleAge)
+ expiryAge = d->maxArticleAge *24*3600;
+
+ return ( expiryAge != -1 && a.pubDate().secsTo(now) > expiryAge);
+}
+
+void Feed::appendArticle(const Article& a)
+{
+ if ( (a.keep() && Settings::doNotExpireImportantArticles()) || ( !usesExpiryByAge() || !isExpired(a) ) ) // if not expired
+ {
+ if (!d->articles.contains(a.guid()))
+ {
+ d->articles[a.guid()] = a;
+ if (!a.isDeleted() && a.status() != Article::Read)
+ setUnread(unread()+1);
+ }
+ }
+}
+
+
+void Feed::fetch(bool followDiscovery)
+{
+ d->followDiscovery = followDiscovery;
+ d->fetchTries = 0;
+
+ // mark all new as unread
+ QValueList<Article> articles = d->articles.values();
+ QValueList<Article>::Iterator it;
+ QValueList<Article>::Iterator en = articles.end();
+ for (it = articles.begin(); it != en; ++it)
+ {
+ if ((*it).status() == Article::New)
+ {
+ (*it).setStatus(Article::Unread);
+ }
+ }
+
+ emit fetchStarted(this);
+
+ tryFetch();
+}
+
+void Feed::slotAbortFetch()
+{
+ if (d->loader)
+ {
+ d->loader->abort();
+ }
+}
+
+void Feed::tryFetch()
+{
+ d->fetchError = false;
+
+ d->loader = RSS::Loader::create( this, SLOT(fetchCompleted(Loader *, Document, Status)) );
+ //connect(d->loader, SIGNAL(progress(unsigned long)), this, SLOT(slotSetProgress(unsigned long)));
+ d->loader->loadFrom( d->xmlUrl, new RSS::FileRetriever );
+}
+
+void Feed::slotImageFetched(const QPixmap& image)
+{
+ if (image.isNull())
+ return;
+ d->imagePixmap=image;
+ d->imagePixmap.save(KGlobal::dirs()->saveLocation("cache", "akregator/Media/")
+ + Utils::fileNameForUrl(d->xmlUrl) +
+".png","PNG");
+ nodeModified();
+}
+
+void Feed::fetchCompleted(RSS::Loader *l, RSS::Document doc, RSS::Status status)
+{
+ // Note that loader instances delete themselves
+ d->loader = 0;
+
+ // fetching wasn't successful:
+ if (status != RSS::Success)
+ {
+ if (status == RSS::Aborted)
+ {
+ d->fetchError = false;
+ emit fetchAborted(this);
+ }
+ else if (d->followDiscovery && (status == RSS::ParseError) && (d->fetchTries < 3) && (l->discoveredFeedURL().isValid()))
+ {
+ d->fetchTries++;
+ d->xmlUrl = l->discoveredFeedURL().url();
+ emit fetchDiscovery(this);
+ tryFetch();
+ }
+ else
+ {
+ d->fetchError = true;
+ d->lastErrorFetch = QDateTime::currentDateTime().toTime_t();
+ emit fetchError(this);
+ }
+ return;
+ }
+
+ loadArticles(); // TODO: make me fly: make this delayed
+
+ // Restore favicon.
+ if (d->favicon.isNull())
+ loadFavicon();
+
+ d->fetchError = false;
+
+ if (doc.image() && d->imagePixmap.isNull())
+ {
+ d->image = *doc.image();
+ connect(&d->image, SIGNAL(gotPixmap(const QPixmap&)), this, SLOT(slotImageFetched(const QPixmap&)));
+ d->image.getPixmap();
+ }
+
+ if (title().isEmpty())
+ setTitle( doc.title() );
+
+ d->description = doc.description();
+ d->htmlUrl = doc.link().url();
+
+ appendArticles(doc);
+
+ d->archive->setLastFetch( QDateTime::currentDateTime().toTime_t());
+ emit fetched(this);
+}
+
+void Feed::loadFavicon()
+{
+ FeedIconManager::self()->fetchIcon(this);
+}
+
+void Feed::slotDeleteExpiredArticles()
+{
+ if ( !usesExpiryByAge() )
+ return;
+
+ QValueList<Article> articles = d->articles.values();
+
+ QValueList<Article>::Iterator en = articles.end();
+
+ setNotificationMode(false);
+
+ // check keep flag only if it should be respected for expiry
+ // the code could be more compact, but we better check
+ // doNotExpiredArticles once instead of in every iteration
+ if (Settings::doNotExpireImportantArticles())
+ {
+ for (QValueList<Article>::Iterator it = articles.begin(); it != en; ++it)
+ {
+ if (!(*it).keep() && isExpired(*it))
+ {
+ (*it).setDeleted();
+ }
+ }
+ }
+ else
+ {
+ for (QValueList<Article>::Iterator it = articles.begin(); it != en; ++it)
+ {
+ if (isExpired(*it))
+ {
+ (*it).setDeleted();
+ }
+ }
+ }
+ setNotificationMode(true);
+}
+
+void Feed::setFavicon(const QPixmap &p)
+{
+ d->favicon = p;
+ nodeModified();
+}
+
+Feed::ArchiveMode Feed::archiveMode() const
+{
+ return d->archiveMode;
+}
+
+void Feed::setArchiveMode(ArchiveMode archiveMode)
+{
+ d->archiveMode = archiveMode;
+}
+
+int Feed::unread() const
+{
+ return d->archive ? d->archive->unread() : 0;
+}
+
+void Feed::setUnread(int unread)
+{
+ if (d->archive && unread != d->archive->unread())
+ {
+ d->archive->setUnread(unread);
+ nodeModified();
+ }
+}
+
+
+void Feed::setArticleDeleted(Article& a)
+{
+ if (!d->deletedArticles.contains(a))
+ d->deletedArticles.append(a);
+
+ if (!d->removedArticlesNotify.contains(a))
+ d->removedArticlesNotify.append(a);
+
+ articlesModified();
+}
+
+void Feed::setArticleChanged(Article& a, int oldStatus)
+{
+ if (oldStatus != -1)
+ {
+ int newStatus = a.status();
+ if (oldStatus == Article::Read && newStatus != Article::Read)
+ setUnread(unread()+1);
+ else if (oldStatus != Article::Read && newStatus == Article::Read)
+ setUnread(unread()-1);
+ }
+ d->updatedArticlesNotify.append(a);
+ articlesModified();
+}
+
+int Feed::totalCount() const
+{
+ return d->articles.count();
+}
+
+TreeNode* Feed::next()
+{
+ if ( nextSibling() )
+ return nextSibling();
+
+ Folder* p = parent();
+ while (p)
+ {
+ if ( p->nextSibling() )
+ return p->nextSibling();
+ else
+ p = p->parent();
+ }
+ return 0;
+}
+
+void Feed::doArticleNotification()
+{
+ if (!d->addedArticlesNotify.isEmpty())
+ {
+ // copy list, otherwise the refcounting in Article::Private breaks for
+ // some reason (causing segfaults)
+ QValueList<Article> l = d->addedArticlesNotify;
+ emit signalArticlesAdded(this, l);
+ d->addedArticlesNotify.clear();
+ }
+ if (!d->updatedArticlesNotify.isEmpty())
+ {
+ // copy list, otherwise the refcounting in Article::Private breaks for
+ // some reason (causing segfaults)
+ QValueList<Article> l = d->updatedArticlesNotify;
+ emit signalArticlesUpdated(this, l);
+ d->updatedArticlesNotify.clear();
+ }
+ if (!d->removedArticlesNotify.isEmpty())
+ {
+ // copy list, otherwise the refcounting in Article::Private breaks for
+ // some reason (causing segfaults)
+ QValueList<Article> l = d->removedArticlesNotify;
+ emit signalArticlesRemoved(this, l);
+ d->removedArticlesNotify.clear();
+ }
+ TreeNode::doArticleNotification();
+}
+
+void Feed::enforceLimitArticleNumber()
+{
+ int limit = -1;
+ if (d->archiveMode == globalDefault && Settings::archiveMode() == Settings::EnumArchiveMode::limitArticleNumber)
+ limit = Settings::maxArticleNumber();
+ else if (d->archiveMode == limitArticleNumber)
+ limit = maxArticleNumber();
+
+ if (limit == -1 || limit >= d->articles.count() - d->deletedArticles.count())
+ return;
+
+ setNotificationMode(false);
+ QValueList<Article> articles = d->articles.values();
+ qHeapSort(articles);
+ QValueList<Article>::Iterator it = articles.begin();
+ QValueList<Article>::Iterator tmp;
+ QValueList<Article>::Iterator en = articles.end();
+
+ int c = 0;
+
+ if (Settings::doNotExpireImportantArticles())
+ {
+ while (it != en)
+ {
+ tmp = it;
+ ++it;
+ if (c < limit)
+ {
+ if (!(*tmp).isDeleted() && !(*tmp).keep())
+ c++;
+ }
+ else if (!(*tmp).keep())
+ (*tmp).setDeleted();
+ }
+ }
+ else
+ {
+ while (it != en)
+ {
+ tmp = it;
+ ++it;
+ if (c < limit && !(*tmp).isDeleted())
+ {
+ c++;
+ }
+ else
+ {
+ (*tmp).setDeleted();
+ }
+ }
+ }
+ setNotificationMode(true);
+}
+
+} // namespace Akregator
+#include "feed.moc"