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.
27 #include "akregatorconfig.h"
29 #include "articlejobs.h"
30 #include "feediconmanager.h"
31 #include "feedstorage.h"
32 #include "fetchqueue.h"
34 #include "notificationmanager.h"
36 #include "treenodevisitor.h"
40 #include <Syndication/Syndication>
42 #include "akregator_debug.h"
51 #include <QDomDocument>
52 #include <QDomElement>
59 #include <QStandardPaths>
61 using Syndication::ItemPtr
;
62 using namespace Akregator
;
64 template<typename Key
, typename Value
, template<typename
, typename
> class Container
>
65 QVector
<Value
> valuesToVector(const Container
<Key
, Value
> &container
)
67 QVector
<Value
> values
;
68 values
.reserve(container
.size());
69 foreach (const Value
&value
, container
) {
75 class Q_DECL_HIDDEN
Akregator::Feed::Private
77 Akregator::Feed
*const q
;
79 explicit Private(Backend::Storage
*storage
, Akregator::Feed
*qq
);
81 Backend::Storage
*storage
;
84 ArchiveMode archiveMode
;
87 bool markImmediatelyAsRead
;
89 bool loadLinkedWebsite
;
92 Syndication::ErrorCode fetchErrorCode
;
95 Syndication::Loader
*loader
;
97 Backend::FeedStorage
*archive
;
103 /** list of feed articles */
104 QHash
<QString
, Article
> articles
;
106 /** list of deleted articles. This contains **/
107 QVector
<Article
> deletedArticles
;
109 /** caches guids of deleted articles for notification */
111 QVector
<Article
> addedArticlesNotify
;
112 QVector
<Article
> removedArticlesNotify
;
113 QVector
<Article
> updatedArticlesNotify
;
116 Syndication::ImagePtr image
;
118 mutable int totalCount
;
119 void setTotalCountDirty() const
125 QString
Akregator::Feed::archiveModeToString(ArchiveMode mode
)
128 case keepAllArticles
:
129 return QStringLiteral("keepAllArticles");
130 case disableArchiving
:
131 return QStringLiteral("disableArchiving");
132 case limitArticleNumber
:
133 return QStringLiteral("limitArticleNumber");
134 case limitArticleAge
:
135 return QStringLiteral("limitArticleAge");
139 return QStringLiteral("globalDefault");
142 Akregator::Feed
*Akregator::Feed::fromOPML(QDomElement e
, Backend::Storage
*storage
)
145 if (!e
.hasAttribute(QStringLiteral("xmlUrl")) && !e
.hasAttribute(QStringLiteral("xmlurl")) && !e
.hasAttribute(QStringLiteral("xmlURL"))) {
149 QString title
= e
.hasAttribute(QStringLiteral("text")) ? e
.attribute(QStringLiteral("text")) : e
.attribute(QStringLiteral("title"));
151 QString xmlUrl
= e
.hasAttribute(QStringLiteral("xmlUrl")) ? e
.attribute(QStringLiteral("xmlUrl")) : e
.attribute(QStringLiteral("xmlurl"));
152 if (xmlUrl
.isEmpty()) {
153 xmlUrl
= e
.attribute(QStringLiteral("xmlURL"));
156 bool useCustomFetchInterval
= e
.attribute(QStringLiteral("useCustomFetchInterval")) == QLatin1String("true");
158 QString htmlUrl
= e
.attribute(QStringLiteral("htmlUrl"));
159 QString description
= e
.attribute(QStringLiteral("description"));
160 int fetchInterval
= e
.attribute(QStringLiteral("fetchInterval")).toInt();
161 ArchiveMode archiveMode
= stringToArchiveMode(e
.attribute(QStringLiteral("archiveMode")));
162 int maxArticleAge
= e
.attribute(QStringLiteral("maxArticleAge")).toUInt();
163 int maxArticleNumber
= e
.attribute(QStringLiteral("maxArticleNumber")).toUInt();
164 bool markImmediatelyAsRead
= e
.attribute(QStringLiteral("markImmediatelyAsRead")) == QLatin1String("true");
165 bool useNotification
= e
.attribute(QStringLiteral("useNotification")) == QLatin1String("true");
166 bool loadLinkedWebsite
= e
.attribute(QStringLiteral("loadLinkedWebsite")) == QLatin1String("true");
167 uint id
= e
.attribute(QStringLiteral("id")).toUInt();
169 Feed
*const feed
= new Feed(storage
);
170 feed
->setTitle(title
);
171 feed
->setXmlUrl(xmlUrl
);
172 feed
->setCustomFetchIntervalEnabled(useCustomFetchInterval
);
173 feed
->setHtmlUrl(htmlUrl
);
175 feed
->setDescription(description
);
176 feed
->setArchiveMode(archiveMode
);
177 feed
->setUseNotification(useNotification
);
178 feed
->setFetchInterval(fetchInterval
);
179 feed
->setMaxArticleAge(maxArticleAge
);
180 feed
->setMaxArticleNumber(maxArticleNumber
);
181 feed
->setMarkImmediatelyAsRead(markImmediatelyAsRead
);
182 feed
->setLoadLinkedWebsite(loadLinkedWebsite
);
183 feed
->loadArticles(); // TODO: make me fly: make this delayed
188 bool Akregator::Feed::accept(TreeNodeVisitor
*visitor
)
190 if (visitor
->visitFeed(this)) {
193 return visitor
->visitTreeNode(this);
197 QVector
<const Folder
*> Akregator::Feed::folders() const
199 return QVector
<const Folder
*>();
202 QVector
<Folder
*> Akregator::Feed::folders()
204 return QVector
<Folder
*>();
207 QVector
<const Akregator::Feed
*> Akregator::Feed::feeds() const
209 QVector
<const Akregator::Feed
*> list
;
214 QVector
<Akregator::Feed
*> Akregator::Feed::feeds()
216 QVector
<Feed
*> list
;
221 Article
Akregator::Feed::findArticle(const QString
&guid
) const
223 return d
->articles
.value(guid
);
226 QVector
<Article
> Akregator::Feed::articles()
228 if (!d
->articlesLoaded
) {
231 return valuesToVector(d
->articles
);
234 Backend::Storage
*Akregator::Feed::storage()
239 void Akregator::Feed::loadArticles()
241 if (d
->articlesLoaded
) {
245 if (!d
->archive
&& d
->storage
) {
246 d
->archive
= d
->storage
->archiveFor(xmlUrl());
249 QStringList list
= d
->archive
->articles();
250 for (QStringList::ConstIterator it
= list
.constBegin(); it
!= list
.constEnd(); ++it
) {
251 Article
mya(*it
, this);
252 d
->articles
[mya
.guid()] = mya
;
253 if (mya
.isDeleted()) {
254 d
->deletedArticles
.append(mya
);
258 d
->articlesLoaded
= true;
259 enforceLimitArticleNumber();
263 void Akregator::Feed::recalcUnreadCount()
265 QVector
<Article
> tarticles
= articles();
266 QVector
<Article
>::ConstIterator it
;
267 QVector
<Article
>::ConstIterator en
= tarticles
.constEnd();
269 int oldUnread
= d
->archive
->unread();
273 for (it
= tarticles
.constBegin(); it
!= en
; ++it
)
274 if (!(*it
).isDeleted() && (*it
).status() != Read
) {
278 if (unread
!= oldUnread
) {
279 d
->archive
->setUnread(unread
);
284 Akregator::Feed::ArchiveMode
Akregator::Feed::stringToArchiveMode(const QString
&str
)
286 if (str
== QLatin1String("globalDefault")) {
287 return globalDefault
;
289 if (str
== QLatin1String("keepAllArticles")) {
290 return keepAllArticles
;
292 if (str
== QLatin1String("disableArchiving")) {
293 return disableArchiving
;
295 if (str
== QLatin1String("limitArticleNumber")) {
296 return limitArticleNumber
;
298 if (str
== QLatin1String("limitArticleAge")) {
299 return limitArticleAge
;
302 return globalDefault
;
305 Akregator::Feed::Private::Private(Backend::Storage
*storage_
, Akregator::Feed
*qq
)
310 archiveMode(globalDefault
),
312 maxArticleNumber(1000),
313 markImmediatelyAsRead(false),
314 useNotification(false),
315 loadLinkedWebsite(false),
317 fetchErrorCode(Syndication::Success
),
319 followDiscovery(false),
321 articlesLoaded(false),
329 Akregator::Feed::Feed(Backend::Storage
*storage
) : TreeNode(), d(new Private(storage
, this))
333 Akregator::Feed::~Feed()
335 FeedIconManager::self()->removeListener(this);
337 emitSignalDestroyed();
342 bool Akregator::Feed::useCustomFetchInterval() const
347 void Akregator::Feed::setCustomFetchIntervalEnabled(bool enabled
)
349 d
->autoFetch
= enabled
;
352 int Akregator::Feed::fetchInterval() const
354 return d
->fetchInterval
;
357 void Akregator::Feed::setFetchInterval(int interval
)
359 d
->fetchInterval
= interval
;
362 int Akregator::Feed::maxArticleAge() const
364 return d
->maxArticleAge
;
367 void Akregator::Feed::setMaxArticleAge(int maxArticleAge
)
369 d
->maxArticleAge
= maxArticleAge
;
372 int Akregator::Feed::maxArticleNumber() const
374 return d
->maxArticleNumber
;
377 void Akregator::Feed::setMaxArticleNumber(int maxArticleNumber
)
379 d
->maxArticleNumber
= maxArticleNumber
;
382 bool Akregator::Feed::markImmediatelyAsRead() const
384 return d
->markImmediatelyAsRead
;
387 bool Akregator::Feed::isFetching() const
389 return d
->loader
!= 0;
392 void Akregator::Feed::setMarkImmediatelyAsRead(bool enabled
)
394 d
->markImmediatelyAsRead
= enabled
;
396 createMarkAsReadJob()->start();
400 void Akregator::Feed::setUseNotification(bool enabled
)
402 d
->useNotification
= enabled
;
405 bool Akregator::Feed::useNotification() const
407 return d
->useNotification
;
410 void Akregator::Feed::setLoadLinkedWebsite(bool enabled
)
412 d
->loadLinkedWebsite
= enabled
;
415 bool Akregator::Feed::loadLinkedWebsite() const
417 return d
->loadLinkedWebsite
;
420 QPixmap
Akregator::Feed::image() const
422 return d
->imagePixmap
;
425 QString
Akregator::Feed::xmlUrl() const
430 void Akregator::Feed::setXmlUrl(const QString
&s
)
433 if (! Settings::fetchOnStartup()) {
434 QTimer::singleShot(KRandom::random() % 4000, this, &Feed::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...
438 QString
Akregator::Feed::htmlUrl() const
443 void Akregator::Feed::setHtmlUrl(const QString
&s
)
448 QString
Akregator::Feed::description() const
450 return d
->description
;
453 void Akregator::Feed::setDescription(const QString
&s
)
458 bool Akregator::Feed::fetchErrorOccurred() const
460 return d
->fetchErrorCode
!= Syndication::Success
;
463 Syndication::ErrorCode
Akregator::Feed::fetchErrorCode() const
465 return d
->fetchErrorCode
;
468 bool Akregator::Feed::isArticlesLoaded() const
470 return d
->articlesLoaded
;
473 QDomElement
Akregator::Feed::toOPML(QDomElement parent
, QDomDocument document
) const
475 QDomElement el
= document
.createElement(QStringLiteral("outline"));
476 el
.setAttribute(QStringLiteral("text"), title());
477 el
.setAttribute(QStringLiteral("title"), title());
478 el
.setAttribute(QStringLiteral("xmlUrl"), d
->xmlUrl
);
479 el
.setAttribute(QStringLiteral("htmlUrl"), d
->htmlUrl
);
480 el
.setAttribute(QStringLiteral("id"), QString::number(id()));
481 el
.setAttribute(QStringLiteral("description"), d
->description
);
482 el
.setAttribute(QStringLiteral("useCustomFetchInterval"), (useCustomFetchInterval() ? QStringLiteral("true") : QStringLiteral("false")));
483 el
.setAttribute(QStringLiteral("fetchInterval"), QString::number(fetchInterval()));
484 el
.setAttribute(QStringLiteral("archiveMode"), archiveModeToString(d
->archiveMode
));
485 el
.setAttribute(QStringLiteral("maxArticleAge"), d
->maxArticleAge
);
486 el
.setAttribute(QStringLiteral("maxArticleNumber"), d
->maxArticleNumber
);
487 if (d
->markImmediatelyAsRead
) {
488 el
.setAttribute(QStringLiteral("markImmediatelyAsRead"), QStringLiteral("true"));
490 if (d
->useNotification
) {
491 el
.setAttribute(QStringLiteral("useNotification"), QStringLiteral("true"));
493 if (d
->loadLinkedWebsite
) {
494 el
.setAttribute(QStringLiteral("loadLinkedWebsite"), QStringLiteral("true"));
496 el
.setAttribute(QStringLiteral("maxArticleNumber"), d
->maxArticleNumber
);
497 el
.setAttribute(QStringLiteral("type"), QStringLiteral("rss")); // despite some additional fields, it is still "rss" OPML
498 el
.setAttribute(QStringLiteral("version"), QStringLiteral("RSS"));
499 parent
.appendChild(el
);
503 KJob
*Akregator::Feed::createMarkAsReadJob()
505 ArticleModifyJob
*job
= new ArticleModifyJob
;
506 Q_FOREACH (const Article
&i
, articles()) {
507 const ArticleId aid
= { xmlUrl(), i
.guid() };
508 job
->setStatus(aid
, Read
);
513 void Akregator::Feed::slotAddToFetchQueue(FetchQueue
*queue
, bool intervalFetchOnly
)
515 if (!intervalFetchOnly
) {
516 queue
->addFeed(this);
520 if (useCustomFetchInterval()) {
521 interval
= fetchInterval() * 60;
522 } else if (Settings::useIntervalFetch()) {
523 interval
= Settings::autoFetchInterval() * 60;
526 uint lastFetch
= d
->archive
->lastFetch();
528 uint now
= QDateTime::currentDateTime().toTime_t();
530 if (interval
> 0 && now
- lastFetch
>= (uint
)interval
) {
531 queue
->addFeed(this);
536 void Akregator::Feed::slotAddFeedIconListener()
538 FeedIconManager::self()->addListener(QUrl(d
->xmlUrl
), this);
541 void Akregator::Feed::appendArticles(const Syndication::FeedPtr
&feed
)
543 d
->setTotalCountDirty();
544 bool changed
= false;
545 const bool notify
= useNotification() || Settings::useNotifications();
547 QList
<ItemPtr
> items
= feed
->items();
548 QList
<ItemPtr
>::ConstIterator it
= items
.constBegin();
549 QList
<ItemPtr
>::ConstIterator en
= items
.constEnd();
553 QVector
<Article
> deletedArticles
= d
->deletedArticles
;
555 for (; it
!= en
; ++it
) {
556 if (!d
->articles
.contains((*it
)->id())) { // article not in list
557 Article
mya(*it
, this);
558 mya
.offsetPubDate(nudge
);
561 d
->addedArticlesNotify
.append(mya
);
563 if (!mya
.isDeleted() && !markImmediatelyAsRead()) {
569 NotificationManager::self()->slotNotifyArticle(mya
);
572 } else { // article is in list
573 // 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.
574 Article old
= d
->articles
[(*it
)->id()];
575 Article
mya(*it
, this);
576 if (!mya
.guidIsHash() && mya
.hash() != old
.hash() && !old
.isDeleted()) {
577 mya
.setKeep(old
.keep());
578 int oldstatus
= old
.status();
581 d
->articles
.remove(old
.guid());
584 mya
.setStatus(oldstatus
);
586 d
->updatedArticlesNotify
.append(mya
);
588 } else if (old
.isDeleted()) {
589 deletedArticles
.removeAll(mya
);
594 QVector
<Article
>::ConstIterator dit
= deletedArticles
.constBegin();
595 QVector
<Article
>::ConstIterator dtmp
;
596 QVector
<Article
>::ConstIterator den
= deletedArticles
.constEnd();
598 // delete articles with delete flag set completely from archive, which aren't in the current feed source anymore
602 d
->articles
.remove((*dtmp
).guid());
603 d
->archive
->deleteArticle((*dtmp
).guid());
604 d
->removedArticlesNotify
.append(*dtmp
);
606 d
->deletedArticles
.removeAll(*dtmp
);
614 bool Akregator::Feed::usesExpiryByAge() const
616 return (d
->archiveMode
== globalDefault
&& Settings::archiveMode() == Settings::EnumArchiveMode::limitArticleAge
) || d
->archiveMode
== limitArticleAge
;
619 bool Akregator::Feed::isExpired(const Article
&a
) const
621 QDateTime now
= QDateTime::currentDateTime();
623 // check whether the feed uses the global default and the default is limitArticleAge
624 if (d
->archiveMode
== globalDefault
&& Settings::archiveMode() == Settings::EnumArchiveMode::limitArticleAge
) {
625 expiryAge
= Settings::maxArticleAge() * 24 * 3600;
626 } else // otherwise check if this feed has limitArticleAge set
627 if (d
->archiveMode
== limitArticleAge
) {
628 expiryAge
= d
->maxArticleAge
* 24 * 3600;
631 return (expiryAge
!= -1 && a
.pubDate().secsTo(now
) > expiryAge
);
634 void Akregator::Feed::appendArticle(const Article
&a
)
636 if ((a
.keep() && Settings::doNotExpireImportantArticles()) || (!usesExpiryByAge() || !isExpired(a
))) { // if not expired
637 if (!d
->articles
.contains(a
.guid())) {
638 d
->articles
[a
.guid()] = a
;
639 if (!a
.isDeleted() && a
.status() != Read
) {
640 setUnread(unread() + 1);
646 void Akregator::Feed::fetch(bool followDiscovery
)
648 d
->followDiscovery
= followDiscovery
;
651 // mark all new as unread
652 for (auto it
= d
->articles
.begin(), end
= d
->articles
.end(); it
!= end
; ++it
) {
653 if ((*it
).status() == New
) {
654 (*it
).setStatus(Unread
);
658 Q_EMIT
fetchStarted(this);
663 void Akregator::Feed::slotAbortFetch()
670 void Akregator::Feed::tryFetch()
672 d
->fetchErrorCode
= Syndication::Success
;
674 d
->loader
= Syndication::Loader::create(this, SLOT(fetchCompleted(Syndication::Loader
*,
675 Syndication::FeedPtr
,
676 Syndication::ErrorCode
)));
677 //connect(d->loader, SIGNAL(progress(ulong)), this, SLOT(slotSetProgress(ulong)));
678 d
->loader
->loadFrom(d
->xmlUrl
);
681 void Akregator::Feed::slotImageFetched(const QPixmap
&image
)
686 void Akregator::Feed::fetchCompleted(Syndication::Loader
*l
, Syndication::FeedPtr doc
, Syndication::ErrorCode status
)
688 // Note that loader instances delete themselves
691 // fetching wasn't successful:
692 if (status
!= Syndication::Success
) {
693 if (status
== Syndication::Aborted
) {
694 d
->fetchErrorCode
= Syndication::Success
;
695 Q_EMIT
fetchAborted(this);
696 } else if (d
->followDiscovery
&& (status
== Syndication::InvalidXml
) && (d
->fetchTries
< 3) && (l
->discoveredFeedURL().isValid())) {
698 d
->xmlUrl
= l
->discoveredFeedURL().url();
699 Q_EMIT
fetchDiscovery(this);
702 d
->fetchErrorCode
= status
;
703 Q_EMIT
fetchError(this);
709 loadArticles(); // TODO: make me fly: make this delayed
711 FeedIconManager::self()->addListener(QUrl(xmlUrl()), this);
713 d
->fetchErrorCode
= Syndication::Success
;
715 if (d
->imagePixmap
.isNull()) {
716 //QString u = d->xmlUrl;
717 QString imageFileName
= QStandardPaths::writableLocation(QStandardPaths::CacheLocation
) + QLatin1Char('/') + QLatin1String("akregator/Media/") + Utils::fileNameForUrl(d
->xmlUrl
) + QLatin1String(".png");
718 d
->imagePixmap
= QPixmap(imageFileName
, "PNG");
720 // if we ain't got the image and the feed provides one, get it....
721 // TODO: reenable image fetching!
722 if (false) { // d->imagePixmap.isNull() && doc.image())
723 //d->image = *doc.image();
724 //connect(&d->image, SIGNAL(gotPixmap(QPixmap)), this, SLOT(slotImageFetched(QPixmap)));
725 //d->image.getPixmap();
729 if (title().isEmpty()) {
730 setTitle(Syndication::htmlToPlainText(doc
->title()));
733 d
->description
= doc
->description();
734 d
->htmlUrl
= doc
->link();
739 Q_EMIT
fetched(this);
742 void Akregator::Feed::markAsFetchedNow()
745 d
->archive
->setLastFetch(QDateTime::currentDateTime().toTime_t());
749 QIcon
Akregator::Feed::icon() const
751 if (fetchErrorOccurred()) {
752 return QIcon::fromTheme(QStringLiteral("dialog-error"));
755 return !d
->favicon
.isNull() ? d
->favicon
: QIcon::fromTheme(QStringLiteral("text-html"));
758 void Akregator::Feed::deleteExpiredArticles(ArticleDeleteJob
*deleteJob
)
760 if (!usesExpiryByAge()) {
764 setNotificationMode(false);
766 QList
<ArticleId
> toDelete
;
767 const QString feedUrl
= xmlUrl();
768 const bool useKeep
= Settings::doNotExpireImportantArticles();
770 Q_FOREACH (const Article
&i
, d
->articles
) {
771 if ((!useKeep
|| !i
.keep()) && isExpired(i
)) {
772 const ArticleId aid
= { feedUrl
, i
.guid() };
773 toDelete
.append(aid
);
777 deleteJob
->appendArticleIds(toDelete
);
778 setNotificationMode(true);
781 void Akregator::Feed::setFavicon(const QIcon
&icon
)
787 void Akregator::Feed::setImage(const QPixmap
&p
)
793 const QString filename
= QStandardPaths::writableLocation(QStandardPaths::CacheLocation
) + QLatin1Char('/') + QString(QLatin1String("akregator/Media/") + Utils::fileNameForUrl(d
->xmlUrl
) + QLatin1String(".png"));
794 QFileInfo
fileInfo(filename
);
795 QDir().mkpath(fileInfo
.absolutePath());
796 d
->imagePixmap
.save(filename
, "PNG");
800 Akregator::Feed::ArchiveMode
Akregator::Feed::archiveMode() const
802 return d
->archiveMode
;
805 void Akregator::Feed::setArchiveMode(ArchiveMode archiveMode
)
807 d
->archiveMode
= archiveMode
;
810 int Akregator::Feed::unread() const
812 return d
->archive
? d
->archive
->unread() : 0;
815 void Akregator::Feed::setUnread(int unread
)
817 if (d
->archive
&& unread
!= d
->archive
->unread()) {
818 d
->archive
->setUnread(unread
);
823 void Akregator::Feed::setArticleDeleted(Article
&a
)
825 d
->setTotalCountDirty();
826 if (!d
->deletedArticles
.contains(a
)) {
827 d
->deletedArticles
.append(a
);
830 d
->updatedArticlesNotify
.append(a
);
834 void Akregator::Feed::setArticleChanged(Article
&a
, int oldStatus
)
836 if (oldStatus
!= -1) {
837 int newStatus
= a
.status();
838 if (oldStatus
== Read
&& newStatus
!= Read
) {
839 setUnread(unread() + 1);
840 } else if (oldStatus
!= Read
&& newStatus
== Read
) {
841 setUnread(unread() - 1);
844 d
->updatedArticlesNotify
.append(a
);
848 int Akregator::Feed::totalCount() const
850 if (d
->totalCount
== -1) {
851 d
->totalCount
= std::count_if(d
->articles
.constBegin(), d
->articles
.constEnd(), [](const Article
& art
) -> bool { return !art
.isDeleted(); });
853 return d
->totalCount
;
856 TreeNode
*Akregator::Feed::next()
859 return nextSibling();
862 Folder
*p
= parent();
864 if (p
->nextSibling()) {
865 return p
->nextSibling();
873 const TreeNode
*Akregator::Feed::next() const
876 return nextSibling();
879 const Folder
*p
= parent();
881 if (p
->nextSibling()) {
882 return p
->nextSibling();
890 void Akregator::Feed::doArticleNotification()
892 if (!d
->addedArticlesNotify
.isEmpty()) {
893 // copy list, otherwise the refcounting in Article::Private breaks for
894 // some reason (causing segfaults)
895 QVector
<Article
> l
= d
->addedArticlesNotify
;
896 Q_EMIT
signalArticlesAdded(this, l
);
897 d
->addedArticlesNotify
.clear();
899 if (!d
->updatedArticlesNotify
.isEmpty()) {
900 // copy list, otherwise the refcounting in Article::Private breaks for
901 // some reason (causing segfaults)
902 QVector
<Article
> l
= d
->updatedArticlesNotify
;
903 Q_EMIT
signalArticlesUpdated(this, l
);
904 d
->updatedArticlesNotify
.clear();
906 if (!d
->removedArticlesNotify
.isEmpty()) {
907 // copy list, otherwise the refcounting in Article::Private breaks for
908 // some reason (causing segfaults)
909 QVector
<Article
> l
= d
->removedArticlesNotify
;
910 Q_EMIT
signalArticlesRemoved(this, l
);
911 d
->removedArticlesNotify
.clear();
913 TreeNode::doArticleNotification();
916 void Akregator::Feed::enforceLimitArticleNumber()
919 if (d
->archiveMode
== globalDefault
&& Settings::archiveMode() == Settings::EnumArchiveMode::limitArticleNumber
) {
920 limit
= Settings::maxArticleNumber();
921 } else if (d
->archiveMode
== limitArticleNumber
) {
922 limit
= maxArticleNumber();
925 if (limit
== -1 || limit
>= d
->articles
.count() - d
->deletedArticles
.count()) {
929 QVector
<Article
> articles
= valuesToVector(d
->articles
);
933 const bool useKeep
= Settings::doNotExpireImportantArticles();
935 Q_FOREACH (Article i
, articles
) { //krazy:exclude=foreach
937 if (!i
.isDeleted() && (!useKeep
|| !i
.keep())) {
940 } else if (!useKeep
|| !i
.keep()) {