Make sure that the archive exists before calling markAsFetchNow.
[kdepim.git] / akregator / src / feed.cpp
blob1c96a2c4fd197015538997105f725abf42c4837b
1 /*
2 This file is part of Akregator.
4 Copyright (C) 2004 Stanislav Karchebny <Stanislav.Karchebny@kdemail.net>
5 2005 Frank Osterfeld <osterfeld@kde.org>
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 As a special exception, permission is given to link this program
22 with any edition of Qt, and distribute the resulting executable,
23 without including the source code for Qt in the source distribution.
26 #include "akregatorconfig.h"
27 #include "feed.h"
28 #include "article.h"
29 #include "articlejobs.h"
30 #include "feediconmanager.h"
31 #include "feedstorage.h"
32 #include "fetchqueue.h"
33 #include "folder.h"
34 #include "notificationmanager.h"
35 #include "storage.h"
36 #include "treenodevisitor.h"
37 #include "types.h"
38 #include "utils.h"
39 #include <syndication/syndication.h>
41 #include <kdebug.h>
42 #include <kglobal.h>
43 #include <KIcon>
44 #include <kstandarddirs.h>
45 #include <kurl.h>
46 #include <KRandom>
48 //#include <qtl.h>
50 #include <QDateTime>
51 #include <QDomDocument>
52 #include <QDomElement>
53 #include <QHash>
54 #include <QIcon>
55 #include <QList>
56 #include <QPixmap>
57 #include <QTimer>
59 #include <boost/bind.hpp>
61 #include <memory>
63 using Syndication::ItemPtr;
64 using namespace Akregator;
65 using namespace boost;
67 class Feed::Private
69 Feed* const q;
70 public:
71 explicit Private( Backend::Storage* storage, Feed* qq );
73 Backend::Storage* storage;
74 bool autoFetch;
75 int fetchInterval;
76 ArchiveMode archiveMode;
77 int maxArticleAge;
78 int maxArticleNumber;
79 bool markImmediatelyAsRead;
80 bool useNotification;
81 bool loadLinkedWebsite;
82 int lastFetched;
84 Syndication::ErrorCode fetchErrorCode;
85 int fetchTries;
86 bool followDiscovery;
87 Syndication::Loader* loader;
88 bool articlesLoaded;
89 Backend::FeedStorage* archive;
91 QString xmlUrl;
92 QString htmlUrl;
93 QString description;
95 /** list of feed articles */
96 QHash<QString, Article> articles;
98 /** list of deleted articles. This contains **/
99 QList<Article> deletedArticles;
101 /** caches guids of deleted articles for notification */
103 QList<Article> addedArticlesNotify;
104 QList<Article> removedArticlesNotify;
105 QList<Article> updatedArticlesNotify;
107 QPixmap imagePixmap;
108 Syndication::ImagePtr image;
109 QIcon favicon;
110 mutable int totalCount;
111 void setTotalCountDirty() const { totalCount = -1; }
114 QString Feed::archiveModeToString(ArchiveMode mode)
116 switch (mode)
118 case keepAllArticles:
119 return "keepAllArticles";
120 case disableArchiving:
121 return "disableArchiving";
122 case limitArticleNumber:
123 return "limitArticleNumber";
124 case limitArticleAge:
125 return "limitArticleAge";
126 default:
127 return "globalDefault";
130 // in a perfect world, this is never reached
132 return "globalDefault";
135 Feed* Feed::fromOPML(QDomElement e, Backend::Storage* storage )
138 if( !e.hasAttribute("xmlUrl") && !e.hasAttribute("xmlurl") && !e.hasAttribute("xmlURL") )
139 return 0;
141 QString title = e.hasAttribute("text") ? e.attribute("text") : e.attribute("title");
143 QString xmlUrl = e.hasAttribute("xmlUrl") ? e.attribute("xmlUrl") : e.attribute("xmlurl");
144 if (xmlUrl.isEmpty())
145 xmlUrl = e.attribute("xmlURL");
147 bool useCustomFetchInterval = e.attribute("useCustomFetchInterval") == "true";
149 QString htmlUrl = e.attribute("htmlUrl");
150 QString description = e.attribute("description");
151 int fetchInterval = e.attribute("fetchInterval").toInt();
152 ArchiveMode archiveMode = stringToArchiveMode(e.attribute("archiveMode"));
153 int maxArticleAge = e.attribute("maxArticleAge").toUInt();
154 int maxArticleNumber = e.attribute("maxArticleNumber").toUInt();
155 bool markImmediatelyAsRead = e.attribute("markImmediatelyAsRead") == "true";
156 bool useNotification = e.attribute("useNotification") == "true";
157 bool loadLinkedWebsite = e.attribute("loadLinkedWebsite") == "true";
158 uint id = e.attribute("id").toUInt();
160 Feed* const feed = new Feed( storage );
161 feed->setTitle(title);
162 feed->setXmlUrl(xmlUrl);
163 feed->setCustomFetchIntervalEnabled(useCustomFetchInterval);
164 feed->setHtmlUrl(htmlUrl);
165 feed->setId(id);
166 feed->setDescription(description);
167 feed->setArchiveMode(archiveMode);
168 feed->setUseNotification(useNotification);
169 feed->setFetchInterval(fetchInterval);
170 feed->setMaxArticleAge(maxArticleAge);
171 feed->setMaxArticleNumber(maxArticleNumber);
172 feed->setMarkImmediatelyAsRead(markImmediatelyAsRead);
173 feed->setLoadLinkedWebsite(loadLinkedWebsite);
174 feed->loadArticles(); // TODO: make me fly: make this delayed
176 return feed;
179 bool Feed::accept(TreeNodeVisitor* visitor)
181 if (visitor->visitFeed(this))
182 return true;
183 else
184 return visitor->visitTreeNode(this);
187 QVector<const Folder*> Feed::folders() const
189 return QVector<const Folder*>();
192 QVector<Folder*> Feed::folders()
194 return QVector<Folder*>();
197 QVector<const Feed*> Feed::feeds() const
199 QVector<const Feed*> list;
200 list.append( this );
201 return list;
204 QVector<Feed*> Feed::feeds()
206 QVector<Feed*> list;
207 list.append( this );
208 return list;
211 Article Feed::findArticle(const QString& guid) const
213 return d->articles[guid];
216 QList<Article> Feed::articles()
218 if (!d->articlesLoaded)
219 loadArticles();
220 return d->articles.values();
223 Backend::Storage* Feed::storage()
225 return d->storage;
228 void Feed::loadArticles()
230 if (d->articlesLoaded)
231 return;
233 if (!d->archive)
234 d->archive = d->storage->archiveFor(xmlUrl());
236 QStringList list = d->archive->articles();
237 for ( QStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it)
239 Article mya(*it, this);
240 d->articles[mya.guid()] = mya;
241 if (mya.isDeleted())
242 d->deletedArticles.append(mya);
245 d->articlesLoaded = true;
246 enforceLimitArticleNumber();
247 recalcUnreadCount();
250 void Feed::recalcUnreadCount()
252 QList<Article> tarticles = articles();
253 QList<Article>::ConstIterator it;
254 QList<Article>::ConstIterator en = tarticles.constEnd();
256 int oldUnread = d->archive->unread();
258 int unread = 0;
260 for (it = tarticles.constBegin(); it != en; ++it)
261 if (!(*it).isDeleted() && (*it).status() != Read)
262 ++unread;
264 if (unread != oldUnread)
266 d->archive->setUnread(unread);
267 nodeModified();
271 Feed::ArchiveMode Feed::stringToArchiveMode(const QString& str)
273 if (str == "globalDefault")
274 return globalDefault;
275 if (str == "keepAllArticles")
276 return keepAllArticles;
277 if (str == "disableArchiving")
278 return disableArchiving;
279 if (str == "limitArticleNumber")
280 return limitArticleNumber;
281 if (str == "limitArticleAge")
282 return limitArticleAge;
284 return globalDefault;
287 Feed::Private::Private( Backend::Storage* storage_, Feed* qq )
288 : q( qq ),
289 storage( storage_ ),
290 autoFetch( false ),
291 fetchInterval( 30 ),
292 archiveMode( globalDefault ),
293 maxArticleAge( 60 ),
294 maxArticleNumber( 1000 ),
295 markImmediatelyAsRead( false ),
296 useNotification( false ),
297 loadLinkedWebsite( false ),
298 lastFetched( 0 ),
299 fetchErrorCode( Syndication::Success ),
300 fetchTries( 0 ),
301 followDiscovery( false ),
302 loader( 0 ),
303 articlesLoaded( false ),
304 archive( 0 ),
305 totalCount( -1 )
307 assert( q );
308 assert( storage );
311 Feed::Feed( Backend::Storage* storage ) : TreeNode(), d( new Private( storage, this ) )
315 Feed::~Feed()
317 FeedIconManager::self()->removeListener( this );
318 slotAbortFetch();
319 emitSignalDestroyed();
320 delete d;
321 d = 0;
324 bool Feed::useCustomFetchInterval() const { return d->autoFetch; }
326 void Feed::setCustomFetchIntervalEnabled(bool enabled) { d->autoFetch = enabled; }
328 int Feed::fetchInterval() const { return d->fetchInterval; }
330 void Feed::setFetchInterval(int interval) { d->fetchInterval = interval; }
332 int Feed::maxArticleAge() const { return d->maxArticleAge; }
334 void Feed::setMaxArticleAge(int maxArticleAge) { d->maxArticleAge = maxArticleAge; }
336 int Feed::maxArticleNumber() const { return d->maxArticleNumber; }
338 void Feed::setMaxArticleNumber(int maxArticleNumber) { d->maxArticleNumber = maxArticleNumber; }
340 bool Feed::markImmediatelyAsRead() const { return d->markImmediatelyAsRead; }
342 bool Feed::isFetching() const { return d->loader != 0; }
344 void Feed::setMarkImmediatelyAsRead(bool enabled)
346 d->markImmediatelyAsRead = enabled;
347 if (enabled)
348 createMarkAsReadJob()->start();
351 void Feed::setUseNotification(bool enabled)
353 d->useNotification = enabled;
356 bool Feed::useNotification() const
358 return d->useNotification;
361 void Feed::setLoadLinkedWebsite(bool enabled)
363 d->loadLinkedWebsite = enabled;
366 bool Feed::loadLinkedWebsite() const
368 return d->loadLinkedWebsite;
371 QPixmap Feed::image() const { return d->imagePixmap; }
373 QString Feed::xmlUrl() const { return d->xmlUrl; }
375 void Feed::setXmlUrl(const QString& s)
377 d->xmlUrl = s;
378 if( ! Settings::fetchOnStartup() )
379 QTimer::singleShot(KRandom::random() % 4000, this, SLOT(slotAddFeedIconListener())); // TODO: let's give a gui some time to show up before starting the fetch when no fetch on startup is used. replace this with something proper later...
382 QString Feed::htmlUrl() const { return d->htmlUrl; }
384 void Feed::setHtmlUrl(const QString& s) { d->htmlUrl = s; }
386 QString Feed::description() const { return d->description; }
388 void Feed::setDescription(const QString& s) { d->description = s; }
390 bool Feed::fetchErrorOccurred() const { return d->fetchErrorCode != Syndication::Success; }
392 Syndication::ErrorCode Feed::fetchErrorCode() const { return d->fetchErrorCode; }
394 bool Feed::isArticlesLoaded() const { return d->articlesLoaded; }
396 QDomElement Feed::toOPML( QDomElement parent, QDomDocument document ) const
398 QDomElement el = document.createElement( "outline" );
399 el.setAttribute( "text", title() );
400 el.setAttribute( "title", title() );
401 el.setAttribute( "xmlUrl", d->xmlUrl );
402 el.setAttribute( "htmlUrl", d->htmlUrl );
403 el.setAttribute( "id", QString::number(id()) );
404 el.setAttribute( "description", d->description );
405 el.setAttribute( "useCustomFetchInterval", (useCustomFetchInterval() ? "true" : "false") );
406 el.setAttribute( "fetchInterval", QString::number(fetchInterval()) );
407 el.setAttribute( "archiveMode", archiveModeToString(d->archiveMode) );
408 el.setAttribute( "maxArticleAge", d->maxArticleAge );
409 el.setAttribute( "maxArticleNumber", d->maxArticleNumber );
410 if (d->markImmediatelyAsRead)
411 el.setAttribute( "markImmediatelyAsRead", "true" );
412 if (d->useNotification)
413 el.setAttribute( "useNotification", "true" );
414 if (d->loadLinkedWebsite)
415 el.setAttribute( "loadLinkedWebsite", "true" );
416 el.setAttribute( "maxArticleNumber", d->maxArticleNumber );
417 el.setAttribute( "type", "rss" ); // despite some additional fields, it is still "rss" OPML
418 el.setAttribute( "version", "RSS" );
419 parent.appendChild( el );
420 return el;
423 KJob* Feed::createMarkAsReadJob()
425 std::auto_ptr<ArticleModifyJob> job( new ArticleModifyJob );
426 Q_FOREACH ( const Article& i, articles() )
428 const ArticleId aid = { xmlUrl(), i.guid() };
429 job->setStatus( aid, Read );
431 return job.release();
434 void Feed::slotAddToFetchQueue(FetchQueue* queue, bool intervalFetchOnly)
436 if (!intervalFetchOnly)
437 queue->addFeed(this);
438 else
440 int interval = -1;
442 if (useCustomFetchInterval() )
443 interval = fetchInterval() * 60;
444 else
445 if ( Settings::useIntervalFetch() )
446 interval = Settings::autoFetchInterval() * 60;
448 uint lastFetch = d->archive->lastFetch();
450 uint now = QDateTime::currentDateTime().toTime_t();
452 if ( interval > 0 && now - lastFetch >= (uint)interval )
453 queue->addFeed(this);
457 void Feed::slotAddFeedIconListener()
459 FeedIconManager::self()->addListener( KUrl( d->xmlUrl ), this );
462 void Feed::appendArticles(const Syndication::FeedPtr feed)
464 d->setTotalCountDirty();
465 bool changed = false;
466 const bool notify = useNotification() || Settings::useNotifications();
468 QList<ItemPtr> items = feed->items();
469 QList<ItemPtr>::ConstIterator it = items.constBegin();
470 QList<ItemPtr>::ConstIterator en = items.constEnd();
473 int nudge=0;
475 QList<Article> deletedArticles = d->deletedArticles;
477 for ( ; it != en; ++it)
479 if ( !d->articles.contains((*it)->id()) ) // article not in list
481 Article mya(*it, this);
482 mya.offsetPubDate(nudge);
483 nudge--;
484 appendArticle(mya);
485 d->addedArticlesNotify.append(mya);
487 if (!mya.isDeleted() && !markImmediatelyAsRead())
488 mya.setStatus(New);
489 else
490 mya.setStatus(Read);
491 if ( notify )
492 NotificationManager::self()->slotNotifyArticle( mya );
493 changed = true;
495 else // article is in list
497 // 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.
498 Article old = d->articles[(*it)->id()];
499 Article mya(*it, this);
500 if (!mya.guidIsHash() && mya.hash() != old.hash() && !old.isDeleted())
502 mya.setKeep(old.keep());
503 int oldstatus = old.status();
504 old.setStatus(Read);
506 d->articles.remove(old.guid());
507 appendArticle(mya);
509 mya.setStatus(oldstatus);
511 d->updatedArticlesNotify.append(mya);
512 changed = true;
514 else if (old.isDeleted())
515 deletedArticles.removeAll(mya);
520 QList<Article>::ConstIterator dit = deletedArticles.constBegin();
521 QList<Article>::ConstIterator dtmp;
522 QList<Article>::ConstIterator den = deletedArticles.constEnd();
524 // delete articles with delete flag set completely from archive, which aren't in the current feed source anymore
525 while (dit != den)
527 dtmp = dit;
528 ++dit;
529 d->articles.remove((*dtmp).guid());
530 d->archive->deleteArticle((*dtmp).guid());
531 d->removedArticlesNotify.append( *dtmp );
532 changed = true;
533 d->deletedArticles.removeAll(*dtmp);
536 if (changed)
537 articlesModified();
540 bool Feed::usesExpiryByAge() const
542 return ( d->archiveMode == globalDefault && Settings::archiveMode() == Settings::EnumArchiveMode::limitArticleAge) || d->archiveMode == limitArticleAge;
545 bool Feed::isExpired(const Article& a) const
547 QDateTime now = QDateTime::currentDateTime();
548 int expiryAge = -1;
549 // check whether the feed uses the global default and the default is limitArticleAge
550 if ( d->archiveMode == globalDefault && Settings::archiveMode() == Settings::EnumArchiveMode::limitArticleAge)
551 expiryAge = Settings::maxArticleAge() *24*3600;
552 else // otherwise check if this feed has limitArticleAge set
553 if ( d->archiveMode == limitArticleAge)
554 expiryAge = d->maxArticleAge *24*3600;
556 return ( expiryAge != -1 && a.pubDate().secsTo(now) > expiryAge);
559 void Feed::appendArticle(const Article& a)
561 if ( (a.keep() && Settings::doNotExpireImportantArticles()) || ( !usesExpiryByAge() || !isExpired(a) ) ) // if not expired
563 if (!d->articles.contains(a.guid()))
565 d->articles[a.guid()] = a;
566 if (!a.isDeleted() && a.status() != Read)
567 setUnread(unread()+1);
573 void Feed::fetch(bool followDiscovery)
575 d->followDiscovery = followDiscovery;
576 d->fetchTries = 0;
578 // mark all new as unread
579 QList<Article> articles = d->articles.values();
580 QList<Article>::Iterator it;
581 QList<Article>::Iterator en = articles.end();
582 for (it = articles.begin(); it != en; ++it)
584 if ((*it).status() == New)
586 (*it).setStatus(Unread);
590 emit fetchStarted(this);
592 tryFetch();
595 void Feed::slotAbortFetch()
597 if (d->loader)
599 d->loader->abort();
603 void Feed::tryFetch()
605 d->fetchErrorCode = Syndication::Success;
607 d->loader = Syndication::Loader::create( this, SLOT(fetchCompleted(Syndication::Loader*,
608 Syndication::FeedPtr,
609 Syndication::ErrorCode)) );
610 //connect(d->loader, SIGNAL(progress(unsigned long)), this, SLOT(slotSetProgress(unsigned long)));
611 d->loader->loadFrom( d->xmlUrl);
614 void Feed::slotImageFetched(const QPixmap& image)
616 setImage(image);
619 void Feed::fetchCompleted(Syndication::Loader *l, Syndication::FeedPtr doc, Syndication::ErrorCode status)
621 // Note that loader instances delete themselves
622 d->loader = 0;
624 // fetching wasn't successful:
625 if (status != Syndication::Success)
627 if (status == Syndication::Aborted)
629 d->fetchErrorCode = Syndication::Success;
630 emit fetchAborted(this);
632 else if (d->followDiscovery && (status == Syndication::InvalidXml) && (d->fetchTries < 3) && (l->discoveredFeedURL().isValid()))
634 d->fetchTries++;
635 d->xmlUrl = l->discoveredFeedURL().url();
636 emit fetchDiscovery(this);
637 tryFetch();
639 else
641 d->fetchErrorCode = status;
642 emit fetchError(this);
644 markAsFetchedNow();
645 return;
648 loadArticles(); // TODO: make me fly: make this delayed
650 FeedIconManager::self()->addListener( KUrl( xmlUrl() ), this );
652 d->fetchErrorCode = Syndication::Success;
654 if (d->imagePixmap.isNull())
656 QString u = d->xmlUrl;
657 QString imageFileName = KGlobal::dirs()->saveLocation("cache", "akregator/Media/")
658 + Utils::fileNameForUrl(d->xmlUrl) + ".png";
659 d->imagePixmap=QPixmap(imageFileName, "PNG");
661 // if we ain't got the image and the feed provides one, get it....
662 // TODO: reenable image fetching!
663 if (false) // d->imagePixmap.isNull() && doc.image())
665 //d->image = *doc.image();
666 //connect(&d->image, SIGNAL(gotPixmap(const QPixmap&)), this, SLOT(slotImageFetched(const QPixmap&)));
667 //d->image.getPixmap();
671 if (title().isEmpty())
672 setTitle( Syndication::htmlToPlainText( doc->title() ) );
674 d->description = doc->description();
675 d->htmlUrl = doc->link();
677 appendArticles(doc);
679 markAsFetchedNow();
680 emit fetched(this);
683 void Feed::markAsFetchedNow()
685 if ( d->archive )
686 d->archive->setLastFetch( QDateTime::currentDateTime().toTime_t());
689 QIcon Feed::icon() const
691 if ( fetchErrorOccurred() )
692 return KIcon("dialog-error");
694 return !d->favicon.isNull() ? d->favicon : KIcon("text-html");
697 void Feed::deleteExpiredArticles( ArticleDeleteJob* deleteJob )
699 if ( !usesExpiryByAge() )
700 return;
702 setNotificationMode(false);
704 const QList<Article> articles = d->articles.values();
705 QList<ArticleId> toDelete;
706 const QString feedUrl = xmlUrl();
707 const bool useKeep = Settings::doNotExpireImportantArticles();
709 Q_FOREACH ( const Article& i, articles )
711 if ( ( !useKeep || !i.keep() ) && isExpired( i ) )
713 const ArticleId aid = { feedUrl, i.guid() };
714 toDelete.append( aid );
718 deleteJob->appendArticleIds( toDelete );
719 setNotificationMode(true);
722 void Feed::setFavicon( const QIcon& icon )
724 d->favicon = icon;
725 nodeModified();
728 void Feed::setImage(const QPixmap &p)
730 if (p.isNull())
731 return;
732 d->imagePixmap=p;
733 d->imagePixmap.save(KGlobal::dirs()->saveLocation("cache", "akregator/Media/")+ Utils::fileNameForUrl(d->xmlUrl) + ".png","PNG");
734 nodeModified();
737 Feed::ArchiveMode Feed::archiveMode() const
739 return d->archiveMode;
742 void Feed::setArchiveMode(ArchiveMode archiveMode)
744 d->archiveMode = archiveMode;
747 int Feed::unread() const
749 return d->archive ? d->archive->unread() : 0;
752 void Feed::setUnread(int unread)
754 if (d->archive && unread != d->archive->unread())
756 d->archive->setUnread(unread);
757 nodeModified();
762 void Feed::setArticleDeleted(Article& a)
764 d->setTotalCountDirty();
765 if (!d->deletedArticles.contains(a))
766 d->deletedArticles.append(a);
768 d->updatedArticlesNotify.append(a);
769 articlesModified();
772 void Feed::setArticleChanged(Article& a, int oldStatus)
774 if (oldStatus != -1)
776 int newStatus = a.status();
777 if (oldStatus == Read && newStatus != Read)
778 setUnread(unread()+1);
779 else if (oldStatus != Read && newStatus == Read)
780 setUnread(unread()-1);
782 d->updatedArticlesNotify.append(a);
783 articlesModified();
786 int Feed::totalCount() const
788 if ( d->totalCount == -1 )
789 d->totalCount = std::count_if( d->articles.constBegin(), d->articles.constEnd(), !bind( &Article::isDeleted, _1 ) );
790 return d->totalCount;
793 TreeNode* Feed::next()
795 if ( nextSibling() )
796 return nextSibling();
798 Folder* p = parent();
799 while (p)
801 if ( p->nextSibling() )
802 return p->nextSibling();
803 else
804 p = p->parent();
806 return 0;
810 const TreeNode* Feed::next() const
812 if ( nextSibling() )
813 return nextSibling();
815 const Folder* p = parent();
816 while (p)
818 if ( p->nextSibling() )
819 return p->nextSibling();
820 else
821 p = p->parent();
823 return 0;
826 void Feed::doArticleNotification()
828 if (!d->addedArticlesNotify.isEmpty())
830 // copy list, otherwise the refcounting in Article::Private breaks for
831 // some reason (causing segfaults)
832 QList<Article> l = d->addedArticlesNotify;
833 emit signalArticlesAdded(this, l);
834 d->addedArticlesNotify.clear();
836 if (!d->updatedArticlesNotify.isEmpty())
838 // copy list, otherwise the refcounting in Article::Private breaks for
839 // some reason (causing segfaults)
840 QList<Article> l = d->updatedArticlesNotify;
841 emit signalArticlesUpdated(this, l);
842 d->updatedArticlesNotify.clear();
844 if (!d->removedArticlesNotify.isEmpty())
846 // copy list, otherwise the refcounting in Article::Private breaks for
847 // some reason (causing segfaults)
848 QList<Article> l = d->removedArticlesNotify;
849 emit signalArticlesRemoved(this, l);
850 d->removedArticlesNotify.clear();
852 TreeNode::doArticleNotification();
855 void Feed::enforceLimitArticleNumber()
857 int limit = -1;
858 if (d->archiveMode == globalDefault && Settings::archiveMode() == Settings::EnumArchiveMode::limitArticleNumber)
859 limit = Settings::maxArticleNumber();
860 else if (d->archiveMode == limitArticleNumber)
861 limit = maxArticleNumber();
863 if (limit == -1 || limit >= d->articles.count() - d->deletedArticles.count())
864 return;
866 QList<Article> articles = d->articles.values();
867 qSort(articles);
869 int c = 0;
870 const bool useKeep = Settings::doNotExpireImportantArticles();
872 Q_FOREACH ( Article i, articles )
874 if (c < limit)
876 if ( !i.isDeleted() && ( !useKeep || !i.keep() ) )
877 ++c;
879 else if ( !useKeep || !i.keep() )
881 i.setDeleted();
886 #include "feed.moc"