00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026 #include <qtimer.h>
00027 #include <qdatetime.h>
00028 #include <qlistview.h>
00029 #include <qdom.h>
00030 #include <qmap.h>
00031 #include <qpixmap.h>
00032 #include <qvaluelist.h>
00033
00034 #include <kurl.h>
00035 #include <kdebug.h>
00036 #include <kglobal.h>
00037 #include <kstandarddirs.h>
00038
00039 #include "akregatorconfig.h"
00040 #include "article.h"
00041 #include "articleinterceptor.h"
00042 #include "feed.h"
00043 #include "folder.h"
00044 #include "fetchqueue.h"
00045 #include "feediconmanager.h"
00046 #include "feedstorage.h"
00047 #include "storage.h"
00048 #include "treenodevisitor.h"
00049 #include "utils.h"
00050
00051 #include "librss/librss.h"
00052
00053 namespace Akregator {
00054
00055 class Feed::FeedPrivate
00056 {
00057 public:
00058 bool autoFetch;
00059 int fetchInterval;
00060 ArchiveMode archiveMode;
00061 int maxArticleAge;
00062 int maxArticleNumber;
00063 bool markImmediatelyAsRead;
00064 bool useNotification;
00065 bool loadLinkedWebsite;
00066
00067 bool fetchError;
00068
00069 int lastErrorFetch;
00070
00071
00072
00073
00074 int fetchTries;
00075 bool followDiscovery;
00076 RSS::Loader* loader;
00077 bool articlesLoaded;
00078 Backend::FeedStorage* archive;
00079
00080 QString xmlUrl;
00081 QString htmlUrl;
00082 QString description;
00083
00085 QMap<QString, Article> articles;
00086
00088 QMap<QString, QStringList> taggedArticles;
00089
00091 QValueList<Article> deletedArticles;
00092
00095 QValueList<Article> addedArticlesNotify;
00096 QValueList<Article> removedArticlesNotify;
00097 QValueList<Article> updatedArticlesNotify;
00098
00099 QPixmap imagePixmap;
00100 RSS::Image image;
00101 QPixmap favicon;
00102 };
00103
00104 QString Feed::archiveModeToString(ArchiveMode mode)
00105 {
00106 switch (mode)
00107 {
00108 case keepAllArticles:
00109 return "keepAllArticles";
00110 case disableArchiving:
00111 return "disableArchiving";
00112 case limitArticleNumber:
00113 return "limitArticleNumber";
00114 case limitArticleAge:
00115 return "limitArticleAge";
00116 default:
00117 return "globalDefault";
00118 }
00119
00120
00121
00122 return "globalDefault";
00123 }
00124
00125 Feed* Feed::fromOPML(QDomElement e)
00126 {
00127
00128 Feed* feed = 0;
00129
00130 if( e.hasAttribute("xmlUrl") || e.hasAttribute("xmlurl") )
00131 {
00132 QString title = e.hasAttribute("text") ? e.attribute("text") : e.attribute("title");
00133
00134 QString xmlUrl = e.hasAttribute("xmlUrl") ? e.attribute("xmlUrl") : e.attribute("xmlurl");
00135
00136 bool useCustomFetchInterval = e.attribute("useCustomFetchInterval") == "true" || e.attribute("autoFetch") == "true";
00137
00138
00139
00140 QString htmlUrl = e.attribute("htmlUrl");
00141 QString description = e.attribute("description");
00142 int fetchInterval = e.attribute("fetchInterval").toInt();
00143 ArchiveMode archiveMode = stringToArchiveMode(e.attribute("archiveMode"));
00144 int maxArticleAge = e.attribute("maxArticleAge").toUInt();
00145 int maxArticleNumber = e.attribute("maxArticleNumber").toUInt();
00146 bool markImmediatelyAsRead = e.attribute("markImmediatelyAsRead") == "true";
00147 bool useNotification = e.attribute("useNotification") == "true";
00148 bool loadLinkedWebsite = e.attribute("loadLinkedWebsite") == "true";
00149 uint id = e.attribute("id").toUInt();
00150
00151 feed = new Feed();
00152 feed->setTitle(title);
00153 feed->setXmlUrl(xmlUrl);
00154 feed->setCustomFetchIntervalEnabled(useCustomFetchInterval);
00155 feed->setHtmlUrl(htmlUrl);
00156 feed->setId(id);
00157 feed->setDescription(description);
00158 feed->setArchiveMode(archiveMode);
00159 feed->setUseNotification(useNotification);
00160 feed->setFetchInterval(fetchInterval);
00161 feed->setMaxArticleAge(maxArticleAge);
00162 feed->setMaxArticleNumber(maxArticleNumber);
00163 feed->setMarkImmediatelyAsRead(markImmediatelyAsRead);
00164 feed->setLoadLinkedWebsite(loadLinkedWebsite);
00165 feed->loadArticles();
00166 feed->loadImage();
00167 }
00168
00169 return feed;
00170 }
00171
00172 bool Feed::accept(TreeNodeVisitor* visitor)
00173 {
00174 if (visitor->visitFeed(this))
00175 return true;
00176 else
00177 return visitor->visitTreeNode(this);
00178 }
00179
00180 QStringList Feed::tags() const
00181 {
00182 return d->archive->tags();
00183 }
00184
00185 Article Feed::findArticle(const QString& guid) const
00186 {
00187 return d->articles[guid];
00188 }
00189
00190 QValueList<Article> Feed::articles(const QString& tag)
00191 {
00192 if (!d->articlesLoaded)
00193 loadArticles();
00194 if (tag.isNull())
00195 return d->articles.values();
00196 else
00197 {
00198 QValueList<Article> tagged;
00199 QStringList guids = d->archive->articles(tag);
00200 for (QStringList::ConstIterator it = guids.begin(); it != guids.end(); ++it)
00201 tagged += d->articles[*it];
00202 return tagged;
00203
00204 }
00205 }
00206
00207 void Feed::loadImage()
00208 {
00209 QString imageFileName = KGlobal::dirs()->saveLocation("cache", "akregator/Media/")
00210 + Utils::fileNameForUrl(d->xmlUrl) +
00211 ".png";
00212 d->imagePixmap.load(imageFileName, "PNG");
00213 }
00214
00215 void Feed::loadArticles()
00216 {
00217 if (d->articlesLoaded)
00218 return;
00219
00220 if (!d->archive)
00221 d->archive = Backend::Storage::getInstance()->archiveFor(xmlUrl());
00222
00223 QStringList list = d->archive->articles();
00224 for ( QStringList::ConstIterator it = list.begin(); it != list.end(); ++it)
00225 {
00226 Article mya(*it, this);
00227 d->articles[mya.guid()] = mya;
00228 if (mya.isDeleted())
00229 d->deletedArticles.append(mya);
00230 }
00231
00232 d->articlesLoaded = true;
00233 enforceLimitArticleNumber();
00234 recalcUnreadCount();
00235 }
00236
00237 void Feed::recalcUnreadCount()
00238 {
00239 QValueList<Article> tarticles = articles();
00240 QValueList<Article>::Iterator it;
00241 QValueList<Article>::Iterator en = tarticles.end();
00242
00243 int oldUnread = d->archive->unread();
00244
00245 int unread = 0;
00246
00247 for (it = tarticles.begin(); it != en; ++it)
00248 if (!(*it).isDeleted() && (*it).status() != Article::Read)
00249 ++unread;
00250
00251 if (unread != oldUnread)
00252 {
00253 d->archive->setUnread(unread);
00254 nodeModified();
00255 }
00256 }
00257
00258 Feed::ArchiveMode Feed::stringToArchiveMode(const QString& str)
00259 {
00260 if (str == "globalDefault")
00261 return globalDefault;
00262 if (str == "keepAllArticles")
00263 return keepAllArticles;
00264 if (str == "disableArchiving")
00265 return disableArchiving;
00266 if (str == "limitArticleNumber")
00267 return limitArticleNumber;
00268 if (str == "limitArticleAge")
00269 return limitArticleAge;
00270
00271 return globalDefault;
00272 }
00273
00274 Feed::Feed() : TreeNode(), d(new FeedPrivate)
00275 {
00276 d->autoFetch = false;
00277 d->fetchInterval = 30;
00278 d->archiveMode = globalDefault;
00279 d->maxArticleAge = 60;
00280 d->maxArticleNumber = 1000;
00281 d->markImmediatelyAsRead = false;
00282 d->useNotification = false;
00283 d->fetchError = false;
00284 d->lastErrorFetch = 0;
00285 d->fetchTries = 0;
00286 d->loader = 0;
00287 d->articlesLoaded = false;
00288 d->archive = 0;
00289 d->loadLinkedWebsite = false;
00290 }
00291
00292 Feed::~Feed()
00293 {
00294 slotAbortFetch();
00295 emitSignalDestroyed();
00296 delete d;
00297 d = 0;
00298 }
00299
00300 bool Feed::useCustomFetchInterval() const { return d->autoFetch; }
00301
00302 void Feed::setCustomFetchIntervalEnabled(bool enabled) { d->autoFetch = enabled; }
00303
00304 int Feed::fetchInterval() const { return d->fetchInterval; }
00305
00306 void Feed::setFetchInterval(int interval) { d->fetchInterval = interval; }
00307
00308 int Feed::maxArticleAge() const { return d->maxArticleAge; }
00309
00310 void Feed::setMaxArticleAge(int maxArticleAge) { d->maxArticleAge = maxArticleAge; }
00311
00312 int Feed::maxArticleNumber() const { return d->maxArticleNumber; }
00313
00314 void Feed::setMaxArticleNumber(int maxArticleNumber) { d->maxArticleNumber = maxArticleNumber; }
00315
00316 bool Feed::markImmediatelyAsRead() const { return d->markImmediatelyAsRead; }
00317
00318 void Feed::setMarkImmediatelyAsRead(bool enabled)
00319 {
00320 d->markImmediatelyAsRead = enabled;
00321 if (enabled)
00322 slotMarkAllArticlesAsRead();
00323 }
00324
00325 void Feed::setUseNotification(bool enabled)
00326 {
00327 d->useNotification = enabled;
00328 }
00329
00330 bool Feed::useNotification() const
00331 {
00332 return d->useNotification;
00333 }
00334
00335 void Feed::setLoadLinkedWebsite(bool enabled)
00336 {
00337 d->loadLinkedWebsite = enabled;
00338 }
00339
00340 bool Feed::loadLinkedWebsite() const
00341 {
00342 return d->loadLinkedWebsite;
00343 }
00344
00345 const QPixmap& Feed::favicon() const { return d->favicon; }
00346
00347 const QPixmap& Feed::image() const { return d->imagePixmap; }
00348
00349 const QString& Feed::xmlUrl() const { return d->xmlUrl; }
00350
00351 void Feed::setXmlUrl(const QString& s) { d->xmlUrl = s; }
00352
00353 const QString& Feed::htmlUrl() const { return d->htmlUrl; }
00354
00355 void Feed::setHtmlUrl(const QString& s) { d->htmlUrl = s; }
00356
00357 const QString& Feed::description() const { return d->description; }
00358
00359 void Feed::setDescription(const QString& s) { d->description = s; }
00360
00361 bool Feed::fetchErrorOccurred() { return d->fetchError; }
00362
00363 bool Feed::isArticlesLoaded() const { return d->articlesLoaded; }
00364
00365
00366 QDomElement Feed::toOPML( QDomElement parent, QDomDocument document ) const
00367 {
00368 QDomElement el = document.createElement( "outline" );
00369 el.setAttribute( "text", title() );
00370 el.setAttribute( "title", title() );
00371 el.setAttribute( "xmlUrl", d->xmlUrl );
00372 el.setAttribute( "htmlUrl", d->htmlUrl );
00373 el.setAttribute( "id", QString::number(id()) );
00374 el.setAttribute( "description", d->description );
00375 el.setAttribute( "useCustomFetchInterval", (useCustomFetchInterval() ? "true" : "false") );
00376 el.setAttribute( "fetchInterval", QString::number(fetchInterval()) );
00377 el.setAttribute( "archiveMode", archiveModeToString(d->archiveMode) );
00378 el.setAttribute( "maxArticleAge", d->maxArticleAge );
00379 el.setAttribute( "maxArticleNumber", d->maxArticleNumber );
00380 if (d->markImmediatelyAsRead)
00381 el.setAttribute( "markImmediatelyAsRead", "true" );
00382 if (d->useNotification)
00383 el.setAttribute( "useNotification", "true" );
00384 if (d->loadLinkedWebsite)
00385 el.setAttribute( "loadLinkedWebsite", "true" );
00386 el.setAttribute( "maxArticleNumber", d->maxArticleNumber );
00387 el.setAttribute( "type", "rss" );
00388 el.setAttribute( "version", "RSS" );
00389 parent.appendChild( el );
00390 return el;
00391 }
00392
00393 void Feed::slotMarkAllArticlesAsRead()
00394 {
00395 if (unread() > 0)
00396 {
00397 setNotificationMode(false, true);
00398 QValueList<Article> tarticles = articles();
00399 QValueList<Article>::Iterator it;
00400 QValueList<Article>::Iterator en = tarticles.end();
00401
00402 for (it = tarticles.begin(); it != en; ++it)
00403 (*it).setStatus(Article::Read);
00404 setNotificationMode(true, true);
00405 }
00406 }
00407 void Feed::slotAddToFetchQueue(FetchQueue* queue, bool intervalFetchOnly)
00408 {
00409 if (!intervalFetchOnly)
00410 queue->addFeed(this);
00411 else
00412 {
00413 uint now = QDateTime::currentDateTime().toTime_t();
00414
00415
00416
00417
00418 if ( fetchErrorOccurred() && now - d->lastErrorFetch <= 30*60 )
00419 return;
00420
00421 int interval = -1;
00422
00423 if (useCustomFetchInterval() )
00424 interval = fetchInterval() * 60;
00425 else
00426 if ( Settings::useIntervalFetch() )
00427 interval = Settings::autoFetchInterval() * 60;
00428
00429 uint lastFetch = d->archive->lastFetch();
00430
00431 if ( interval > 0 && now - lastFetch >= (uint)interval )
00432 queue->addFeed(this);
00433 }
00434 }
00435
00436
00437 void Feed::appendArticles(const RSS::Document &doc)
00438 {
00439 bool changed = false;
00440
00441 RSS::Article::List d_articles = doc.articles();
00442 RSS::Article::List::ConstIterator it;
00443 RSS::Article::List::ConstIterator en = d_articles.end();
00444
00445 int nudge=0;
00446
00447 QValueList<Article> deletedArticles = d->deletedArticles;
00448
00449 for (it = d_articles.begin(); it != en; ++it)
00450 {
00451 if ( !d->articles.contains((*it).guid()) )
00452 {
00453 Article mya(*it, this);
00454 mya.offsetPubDate(nudge);
00455 nudge--;
00456 appendArticle(mya);
00457
00458 QValueList<ArticleInterceptor*> interceptors = ArticleInterceptorManager::self()->interceptors();
00459 for (QValueList<ArticleInterceptor*>::ConstIterator it = interceptors.begin(); it != interceptors.end(); ++it)
00460 (*it)->processArticle(mya);
00461
00462 d->addedArticlesNotify.append(mya);
00463
00464 if (!mya.isDeleted() && !markImmediatelyAsRead())
00465 mya.setStatus(Article::New);
00466 else
00467 mya.setStatus(Article::Read);
00468
00469 changed = true;
00470 }
00471 else
00472 {
00473
00474 Article old = d->articles[(*it).guid()];
00475 Article mya(*it, this);
00476 if (!mya.guidIsHash() && mya.hash() != old.hash() && !old.isDeleted())
00477 {
00478 mya.setKeep(old.keep());
00479 int oldstatus = old.status();
00480 old.setStatus(Article::Read);
00481
00482 d->articles.remove(old.guid());
00483 appendArticle(mya);
00484
00485 mya.setStatus(oldstatus);
00486
00487 d->updatedArticlesNotify.append(mya);
00488 changed = true;
00489 }
00490 else if (old.isDeleted())
00491 deletedArticles.remove(mya);
00492 }
00493 }
00494
00495 QValueList<Article>::ConstIterator dit = deletedArticles.begin();
00496 QValueList<Article>::ConstIterator dtmp;
00497 QValueList<Article>::ConstIterator den = deletedArticles.end();
00498
00499
00500 while (dit != den)
00501 {
00502 dtmp = dit;
00503 ++dit;
00504 d->articles.remove((*dtmp).guid());
00505 d->archive->deleteArticle((*dtmp).guid());
00506 d->deletedArticles.remove(*dtmp);
00507 }
00508
00509 if (changed)
00510 articlesModified();
00511 }
00512
00513 bool Feed::usesExpiryByAge() const
00514 {
00515 return ( d->archiveMode == globalDefault && Settings::archiveMode() == Settings::EnumArchiveMode::limitArticleAge) || d->archiveMode == limitArticleAge;
00516 }
00517
00518 bool Feed::isExpired(const Article& a) const
00519 {
00520 QDateTime now = QDateTime::currentDateTime();
00521 int expiryAge = -1;
00522
00523 if ( d->archiveMode == globalDefault && Settings::archiveMode() == Settings::EnumArchiveMode::limitArticleAge)
00524 expiryAge = Settings::maxArticleAge() *24*3600;
00525 else
00526 if ( d->archiveMode == limitArticleAge)
00527 expiryAge = d->maxArticleAge *24*3600;
00528
00529 return ( expiryAge != -1 && a.pubDate().secsTo(now) > expiryAge);
00530 }
00531
00532 void Feed::appendArticle(const Article& a)
00533 {
00534 if ( (a.keep() && Settings::doNotExpireImportantArticles()) || ( !usesExpiryByAge() || !isExpired(a) ) )
00535 {
00536 if (!d->articles.contains(a.guid()))
00537 {
00538 d->articles[a.guid()] = a;
00539 if (!a.isDeleted() && a.status() != Article::Read)
00540 setUnread(unread()+1);
00541 }
00542 }
00543 }
00544
00545
00546 void Feed::fetch(bool followDiscovery)
00547 {
00548 d->followDiscovery = followDiscovery;
00549 d->fetchTries = 0;
00550
00551
00552 QValueList<Article> articles = d->articles.values();
00553 QValueList<Article>::Iterator it;
00554 QValueList<Article>::Iterator en = articles.end();
00555 for (it = articles.begin(); it != en; ++it)
00556 {
00557 if ((*it).status() == Article::New)
00558 {
00559 (*it).setStatus(Article::Unread);
00560 }
00561 }
00562
00563 emit fetchStarted(this);
00564
00565 tryFetch();
00566 }
00567
00568 void Feed::slotAbortFetch()
00569 {
00570 if (d->loader)
00571 {
00572 d->loader->abort();
00573 }
00574 }
00575
00576 void Feed::tryFetch()
00577 {
00578 d->fetchError = false;
00579
00580 d->loader = RSS::Loader::create( this, SLOT(fetchCompleted(Loader *, Document, Status)) );
00581
00582 d->loader->loadFrom( d->xmlUrl, new RSS::FileRetriever );
00583 }
00584
00585 void Feed::slotImageFetched(const QPixmap& image)
00586 {
00587 if (image.isNull())
00588 return;
00589 d->imagePixmap=image;
00590 d->imagePixmap.save(KGlobal::dirs()->saveLocation("cache", "akregator/Media/")
00591 + Utils::fileNameForUrl(d->xmlUrl) +
00592 ".png","PNG");
00593 nodeModified();
00594 }
00595
00596 void Feed::fetchCompleted(RSS::Loader *l, RSS::Document doc, RSS::Status status)
00597 {
00598
00599 d->loader = 0;
00600
00601
00602 if (status != RSS::Success)
00603 {
00604 if (status == RSS::Aborted)
00605 {
00606 d->fetchError = false;
00607 emit fetchAborted(this);
00608 }
00609 else if (d->followDiscovery && (status == RSS::ParseError) && (d->fetchTries < 3) && (l->discoveredFeedURL().isValid()))
00610 {
00611 d->fetchTries++;
00612 d->xmlUrl = l->discoveredFeedURL().url();
00613 emit fetchDiscovery(this);
00614 tryFetch();
00615 }
00616 else
00617 {
00618 d->fetchError = true;
00619 d->lastErrorFetch = QDateTime::currentDateTime().toTime_t();
00620 emit fetchError(this);
00621 }
00622 return;
00623 }
00624
00625 loadArticles();
00626
00627
00628 if (d->favicon.isNull())
00629 loadFavicon();
00630
00631 d->fetchError = false;
00632
00633 if (doc.image() && d->imagePixmap.isNull())
00634 {
00635 d->image = *doc.image();
00636 connect(&d->image, SIGNAL(gotPixmap(const QPixmap&)), this, SLOT(slotImageFetched(const QPixmap&)));
00637 d->image.getPixmap();
00638 }
00639
00640 if (title().isEmpty())
00641 setTitle( doc.title() );
00642
00643 d->description = doc.description();
00644 d->htmlUrl = doc.link().url();
00645
00646 appendArticles(doc);
00647
00648 d->archive->setLastFetch( QDateTime::currentDateTime().toTime_t());
00649 emit fetched(this);
00650 }
00651
00652 void Feed::loadFavicon()
00653 {
00654 FeedIconManager::self()->fetchIcon(this);
00655 }
00656
00657 void Feed::slotDeleteExpiredArticles()
00658 {
00659 if ( !usesExpiryByAge() )
00660 return;
00661
00662 QValueList<Article> articles = d->articles.values();
00663
00664 QValueList<Article>::Iterator en = articles.end();
00665
00666 setNotificationMode(false);
00667
00668
00669
00670
00671 if (Settings::doNotExpireImportantArticles())
00672 {
00673 for (QValueList<Article>::Iterator it = articles.begin(); it != en; ++it)
00674 {
00675 if (!(*it).keep() && isExpired(*it))
00676 {
00677 (*it).setDeleted();
00678 }
00679 }
00680 }
00681 else
00682 {
00683 for (QValueList<Article>::Iterator it = articles.begin(); it != en; ++it)
00684 {
00685 if (isExpired(*it))
00686 {
00687 (*it).setDeleted();
00688 }
00689 }
00690 }
00691 setNotificationMode(true);
00692 }
00693
00694 void Feed::setFavicon(const QPixmap &p)
00695 {
00696 d->favicon = p;
00697 nodeModified();
00698 }
00699
00700 Feed::ArchiveMode Feed::archiveMode() const
00701 {
00702 return d->archiveMode;
00703 }
00704
00705 void Feed::setArchiveMode(ArchiveMode archiveMode)
00706 {
00707 d->archiveMode = archiveMode;
00708 }
00709
00710 int Feed::unread() const
00711 {
00712 return d->archive ? d->archive->unread() : 0;
00713 }
00714
00715 void Feed::setUnread(int unread)
00716 {
00717 if (d->archive && unread != d->archive->unread())
00718 {
00719 d->archive->setUnread(unread);
00720 nodeModified();
00721 }
00722 }
00723
00724
00725 void Feed::setArticleDeleted(Article& a)
00726 {
00727 if (!d->deletedArticles.contains(a))
00728 d->deletedArticles.append(a);
00729
00730 if (!d->removedArticlesNotify.contains(a))
00731 d->removedArticlesNotify.append(a);
00732
00733 articlesModified();
00734 }
00735
00736 void Feed::setArticleChanged(Article& a, int oldStatus)
00737 {
00738 if (oldStatus != -1)
00739 {
00740 int newStatus = a.status();
00741 if (oldStatus == Article::Read && newStatus != Article::Read)
00742 setUnread(unread()+1);
00743 else if (oldStatus != Article::Read && newStatus == Article::Read)
00744 setUnread(unread()-1);
00745 }
00746 d->updatedArticlesNotify.append(a);
00747 articlesModified();
00748 }
00749
00750 int Feed::totalCount() const
00751 {
00752 return d->articles.count();
00753 }
00754
00755 TreeNode* Feed::next()
00756 {
00757 if ( nextSibling() )
00758 return nextSibling();
00759
00760 Folder* p = parent();
00761 while (p)
00762 {
00763 if ( p->nextSibling() )
00764 return p->nextSibling();
00765 else
00766 p = p->parent();
00767 }
00768 return 0;
00769 }
00770
00771 void Feed::doArticleNotification()
00772 {
00773 if (!d->addedArticlesNotify.isEmpty())
00774 {
00775
00776
00777 QValueList<Article> l = d->addedArticlesNotify;
00778 emit signalArticlesAdded(this, l);
00779 d->addedArticlesNotify.clear();
00780 }
00781 if (!d->updatedArticlesNotify.isEmpty())
00782 {
00783
00784
00785 QValueList<Article> l = d->updatedArticlesNotify;
00786 emit signalArticlesUpdated(this, l);
00787 d->updatedArticlesNotify.clear();
00788 }
00789 if (!d->removedArticlesNotify.isEmpty())
00790 {
00791
00792
00793 QValueList<Article> l = d->removedArticlesNotify;
00794 emit signalArticlesRemoved(this, l);
00795 d->removedArticlesNotify.clear();
00796 }
00797 TreeNode::doArticleNotification();
00798 }
00799
00800 void Feed::enforceLimitArticleNumber()
00801 {
00802 int limit = -1;
00803 if (d->archiveMode == globalDefault && Settings::archiveMode() == Settings::EnumArchiveMode::limitArticleNumber)
00804 limit = Settings::maxArticleNumber();
00805 else if (d->archiveMode == limitArticleNumber)
00806 limit = maxArticleNumber();
00807
00808 if (limit == -1 || limit >= d->articles.count() - d->deletedArticles.count())
00809 return;
00810
00811 setNotificationMode(false);
00812 QValueList<Article> articles = d->articles.values();
00813 qHeapSort(articles);
00814 QValueList<Article>::Iterator it = articles.begin();
00815 QValueList<Article>::Iterator tmp;
00816 QValueList<Article>::Iterator en = articles.end();
00817
00818 int c = 0;
00819
00820 if (Settings::doNotExpireImportantArticles())
00821 {
00822 while (it != en)
00823 {
00824 tmp = it;
00825 ++it;
00826 if (c < limit)
00827 {
00828 if (!(*tmp).isDeleted() && !(*tmp).keep())
00829 c++;
00830 }
00831 else if (!(*tmp).keep())
00832 (*tmp).setDeleted();
00833 }
00834 }
00835 else
00836 {
00837 while (it != en)
00838 {
00839 tmp = it;
00840 ++it;
00841 if (c < limit && !(*tmp).isDeleted())
00842 {
00843 c++;
00844 }
00845 else
00846 {
00847 (*tmp).setDeleted();
00848 }
00849 }
00850 }
00851 setNotificationMode(true);
00852 }
00853
00854 }
00855 #include "feed.moc"