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