feed.cpp
00001 /* 00002 This file is part of Akregator. 00003 00004 Copyright (C) 2004 Stanislav Karchebny <Stanislav.Karchebny@kdemail.net> 00005 2005 Frank Osterfeld <frank.osterfeld at kdemail.net> 00006 00007 This program is free software; you can redistribute it and/or modify 00008 it under the terms of the GNU General Public License as published by 00009 the Free Software Foundation; either version 2 of the License, or 00010 (at your option) any later version. 00011 00012 This program is distributed in the hope that it will be useful, 00013 but WITHOUT ANY WARRANTY; without even the implied warranty of 00014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00015 GNU General Public License for more details. 00016 00017 You should have received a copy of the GNU General Public License 00018 along with this program; if not, write to the Free Software 00019 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 00020 00021 As a special exception, permission is given to link this program 00022 with any edition of TQt, and distribute the resulting executable, 00023 without including the source code for TQt in the source distribution. 00024 */ 00025 00026 #include <tqtimer.h> 00027 #include <tqdatetime.h> 00028 #include <tqlistview.h> 00029 #include <tqdom.h> 00030 #include <tqmap.h> 00031 #include <tqpixmap.h> 00032 #include <tqvaluelist.h> 00033 00034 #include <kurl.h> 00035 #include <kdebug.h> 00036 #include <tdeglobal.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; // save time of last fetch that went wrong. 00070 // != lastFetch property from the archive 00071 // (that saves the last _successfull fetch!) 00072 // workaround for 3.5.x 00073 00074 int fetchTries; 00075 bool followDiscovery; 00076 RSS::Loader* loader; 00077 bool articlesLoaded; 00078 Backend::FeedStorage* archive; 00079 00080 TQString xmlUrl; 00081 TQString htmlUrl; 00082 TQString description; 00083 00085 TQMap<TQString, Article> articles; 00086 00088 TQMap<TQString, TQStringList> taggedArticles; 00089 00091 TQValueList<Article> deletedArticles; 00092 00095 TQValueList<Article> addedArticlesNotify; 00096 TQValueList<Article> removedArticlesNotify; 00097 TQValueList<Article> updatedArticlesNotify; 00098 00099 TQPixmap imagePixmap; 00100 RSS::Image image; 00101 TQPixmap favicon; 00102 }; 00103 00104 TQString 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 // in a perfect world, this is never reached 00121 00122 return "globalDefault"; 00123 } 00124 00125 Feed* Feed::fromOPML(TQDomElement e) 00126 { 00127 00128 Feed* feed = 0; 00129 00130 if( e.hasAttribute("xmlUrl") || e.hasAttribute("xmlurl") || e.hasAttribute("xmlURL") ) 00131 { 00132 TQString title = e.hasAttribute("text") ? e.attribute("text") : e.attribute("title"); 00133 00134 TQString xmlUrl = e.hasAttribute("xmlUrl") ? e.attribute("xmlUrl") : e.attribute("xmlurl"); 00135 if (xmlUrl.isEmpty()) 00136 xmlUrl = e.attribute("xmlURL"); 00137 00138 bool useCustomFetchInterval = e.attribute("useCustomFetchInterval") == "true" || e.attribute("autoFetch") == "true"; 00139 // "autoFetch" is used in 3.4 00140 // Will be removed in KDE4 00141 00142 TQString htmlUrl = e.attribute("htmlUrl"); 00143 TQString description = e.attribute("description"); 00144 int fetchInterval = e.attribute("fetchInterval").toInt(); 00145 ArchiveMode archiveMode = stringToArchiveMode(e.attribute("archiveMode")); 00146 int maxArticleAge = e.attribute("maxArticleAge").toUInt(); 00147 int maxArticleNumber = e.attribute("maxArticleNumber").toUInt(); 00148 bool markImmediatelyAsRead = e.attribute("markImmediatelyAsRead") == "true"; 00149 bool useNotification = e.attribute("useNotification") == "true"; 00150 bool loadLinkedWebsite = e.attribute("loadLinkedWebsite") == "true"; 00151 uint id = e.attribute("id").toUInt(); 00152 00153 feed = new Feed(); 00154 feed->setTitle(title); 00155 feed->setXmlUrl(xmlUrl); 00156 feed->setCustomFetchIntervalEnabled(useCustomFetchInterval); 00157 feed->setHtmlUrl(htmlUrl); 00158 feed->setId(id); 00159 feed->setDescription(description); 00160 feed->setArchiveMode(archiveMode); 00161 feed->setUseNotification(useNotification); 00162 feed->setFetchInterval(fetchInterval); 00163 feed->setMaxArticleAge(maxArticleAge); 00164 feed->setMaxArticleNumber(maxArticleNumber); 00165 feed->setMarkImmediatelyAsRead(markImmediatelyAsRead); 00166 feed->setLoadLinkedWebsite(loadLinkedWebsite); 00167 feed->loadArticles(); // TODO: make me fly: make this delayed 00168 feed->loadImage(); 00169 } 00170 00171 return feed; 00172 } 00173 00174 bool Feed::accept(TreeNodeVisitor* visitor) 00175 { 00176 if (visitor->visitFeed(this)) 00177 return true; 00178 else 00179 return visitor->visitTreeNode(this); 00180 } 00181 00182 TQStringList Feed::tags() const 00183 { 00184 return d->archive->tags(); 00185 } 00186 00187 Article Feed::findArticle(const TQString& guid) const 00188 { 00189 return d->articles[guid]; 00190 } 00191 00192 TQValueList<Article> Feed::articles(const TQString& tag) 00193 { 00194 if (!d->articlesLoaded) 00195 loadArticles(); 00196 if (tag.isNull()) 00197 return d->articles.values(); 00198 else 00199 { 00200 TQValueList<Article> tagged; 00201 TQStringList guids = d->archive->articles(tag); 00202 for (TQStringList::ConstIterator it = guids.begin(); it != guids.end(); ++it) 00203 tagged += d->articles[*it]; 00204 return tagged; 00205 00206 } 00207 } 00208 00209 void Feed::loadImage() 00210 { 00211 TQString imageFileName = TDEGlobal::dirs()->saveLocation("cache", "akregator/Media/") 00212 + Utils::fileNameForUrl(d->xmlUrl) + 00213 ".png"; 00214 d->imagePixmap.load(imageFileName, "PNG"); 00215 } 00216 00217 void Feed::loadArticles() 00218 { 00219 if (d->articlesLoaded) 00220 return; 00221 00222 if (!d->archive) 00223 d->archive = Backend::Storage::getInstance()->archiveFor(xmlUrl()); 00224 00225 TQStringList list = d->archive->articles(); 00226 for ( TQStringList::ConstIterator it = list.begin(); it != list.end(); ++it) 00227 { 00228 Article mya(*it, this); 00229 d->articles[mya.guid()] = mya; 00230 if (mya.isDeleted()) 00231 d->deletedArticles.append(mya); 00232 } 00233 00234 d->articlesLoaded = true; 00235 enforceLimitArticleNumber(); 00236 recalcUnreadCount(); 00237 } 00238 00239 void Feed::recalcUnreadCount() 00240 { 00241 TQValueList<Article> tarticles = articles(); 00242 TQValueList<Article>::Iterator it; 00243 TQValueList<Article>::Iterator en = tarticles.end(); 00244 00245 int oldUnread = d->archive->unread(); 00246 00247 int unread = 0; 00248 00249 for (it = tarticles.begin(); it != en; ++it) 00250 if (!(*it).isDeleted() && (*it).status() != Article::Read) 00251 ++unread; 00252 00253 if (unread != oldUnread) 00254 { 00255 d->archive->setUnread(unread); 00256 nodeModified(); 00257 } 00258 } 00259 00260 Feed::ArchiveMode Feed::stringToArchiveMode(const TQString& str) 00261 { 00262 if (str == "globalDefault") 00263 return globalDefault; 00264 if (str == "keepAllArticles") 00265 return keepAllArticles; 00266 if (str == "disableArchiving") 00267 return disableArchiving; 00268 if (str == "limitArticleNumber") 00269 return limitArticleNumber; 00270 if (str == "limitArticleAge") 00271 return limitArticleAge; 00272 00273 return globalDefault; 00274 } 00275 00276 Feed::Feed() : TreeNode(), d(new FeedPrivate) 00277 { 00278 d->autoFetch = false; 00279 d->fetchInterval = 30; 00280 d->archiveMode = globalDefault; 00281 d->maxArticleAge = 60; 00282 d->maxArticleNumber = 1000; 00283 d->markImmediatelyAsRead = false; 00284 d->useNotification = false; 00285 d->fetchError = false; 00286 d->lastErrorFetch = 0; 00287 d->fetchTries = 0; 00288 d->loader = 0; 00289 d->articlesLoaded = false; 00290 d->archive = 0; 00291 d->loadLinkedWebsite = false; 00292 } 00293 00294 Feed::~Feed() 00295 { 00296 slotAbortFetch(); 00297 emitSignalDestroyed(); 00298 delete d; 00299 d = 0; 00300 } 00301 00302 bool Feed::useCustomFetchInterval() const { return d->autoFetch; } 00303 00304 void Feed::setCustomFetchIntervalEnabled(bool enabled) { d->autoFetch = enabled; } 00305 00306 int Feed::fetchInterval() const { return d->fetchInterval; } 00307 00308 void Feed::setFetchInterval(int interval) { d->fetchInterval = interval; } 00309 00310 int Feed::maxArticleAge() const { return d->maxArticleAge; } 00311 00312 void Feed::setMaxArticleAge(int maxArticleAge) { d->maxArticleAge = maxArticleAge; } 00313 00314 int Feed::maxArticleNumber() const { return d->maxArticleNumber; } 00315 00316 void Feed::setMaxArticleNumber(int maxArticleNumber) { d->maxArticleNumber = maxArticleNumber; } 00317 00318 bool Feed::markImmediatelyAsRead() const { return d->markImmediatelyAsRead; } 00319 00320 void Feed::setMarkImmediatelyAsRead(bool enabled) 00321 { 00322 d->markImmediatelyAsRead = enabled; 00323 if (enabled) 00324 slotMarkAllArticlesAsRead(); 00325 } 00326 00327 void Feed::setUseNotification(bool enabled) 00328 { 00329 d->useNotification = enabled; 00330 } 00331 00332 bool Feed::useNotification() const 00333 { 00334 return d->useNotification; 00335 } 00336 00337 void Feed::setLoadLinkedWebsite(bool enabled) 00338 { 00339 d->loadLinkedWebsite = enabled; 00340 } 00341 00342 bool Feed::loadLinkedWebsite() const 00343 { 00344 return d->loadLinkedWebsite; 00345 } 00346 00347 const TQPixmap& Feed::favicon() const { return d->favicon; } 00348 00349 const TQPixmap& Feed::image() const { return d->imagePixmap; } 00350 00351 const TQString& Feed::xmlUrl() const { return d->xmlUrl; } 00352 00353 void Feed::setXmlUrl(const TQString& s) { d->xmlUrl = s; } 00354 00355 const TQString& Feed::htmlUrl() const { return d->htmlUrl; } 00356 00357 void Feed::setHtmlUrl(const TQString& s) { d->htmlUrl = s; } 00358 00359 const TQString& Feed::description() const { return d->description; } 00360 00361 void Feed::setDescription(const TQString& s) { d->description = s; } 00362 00363 bool Feed::fetchErrorOccurred() { return d->fetchError; } 00364 00365 bool Feed::isArticlesLoaded() const { return d->articlesLoaded; } 00366 00367 00368 TQDomElement Feed::toOPML( TQDomElement parent, TQDomDocument document ) const 00369 { 00370 TQDomElement el = document.createElement( "outline" ); 00371 el.setAttribute( "text", title() ); 00372 el.setAttribute( "title", title() ); 00373 el.setAttribute( "xmlUrl", d->xmlUrl ); 00374 el.setAttribute( "htmlUrl", d->htmlUrl ); 00375 el.setAttribute( "id", TQString::number(id()) ); 00376 el.setAttribute( "description", d->description ); 00377 el.setAttribute( "useCustomFetchInterval", (useCustomFetchInterval() ? "true" : "false") ); 00378 el.setAttribute( "fetchInterval", TQString::number(fetchInterval()) ); 00379 el.setAttribute( "archiveMode", archiveModeToString(d->archiveMode) ); 00380 el.setAttribute( "maxArticleAge", d->maxArticleAge ); 00381 el.setAttribute( "maxArticleNumber", d->maxArticleNumber ); 00382 if (d->markImmediatelyAsRead) 00383 el.setAttribute( "markImmediatelyAsRead", "true" ); 00384 if (d->useNotification) 00385 el.setAttribute( "useNotification", "true" ); 00386 if (d->loadLinkedWebsite) 00387 el.setAttribute( "loadLinkedWebsite", "true" ); 00388 el.setAttribute( "maxArticleNumber", d->maxArticleNumber ); 00389 el.setAttribute( "type", "rss" ); // despite some additional fields, its still "rss" OPML 00390 el.setAttribute( "version", "RSS" ); 00391 parent.appendChild( el ); 00392 return el; 00393 } 00394 00395 void Feed::slotMarkAllArticlesAsRead() 00396 { 00397 if (unread() > 0) 00398 { 00399 setNotificationMode(false, true); 00400 TQValueList<Article> tarticles = articles(); 00401 TQValueList<Article>::Iterator it; 00402 TQValueList<Article>::Iterator en = tarticles.end(); 00403 00404 for (it = tarticles.begin(); it != en; ++it) 00405 (*it).setStatus(Article::Read); 00406 setNotificationMode(true, true); 00407 } 00408 } 00409 void Feed::slotAddToFetchQueue(FetchQueue* queue, bool intervalFetchOnly) 00410 { 00411 if (!intervalFetchOnly) 00412 queue->addFeed(this); 00413 else 00414 { 00415 uint now = TQDateTime::currentDateTime().toTime_t(); 00416 00417 // workaround for 3.5.x: if the last fetch went wrong, try again after 30 minutes 00418 // this fixes annoying behaviour of akregator, especially when the host is reachable 00419 // but Akregator can't parse the feed (the host is hammered every minute then) 00420 if ( fetchErrorOccurred() && now - d->lastErrorFetch <= 30*60 ) 00421 return; 00422 00423 int interval = -1; 00424 00425 if (useCustomFetchInterval() ) 00426 interval = fetchInterval() * 60; 00427 else 00428 if ( Settings::useIntervalFetch() ) 00429 interval = Settings::autoFetchInterval() * 60; 00430 00431 uint lastFetch = d->archive->lastFetch(); 00432 00433 if ( interval > 0 && now - lastFetch >= (uint)interval ) 00434 queue->addFeed(this); 00435 } 00436 } 00437 00438 00439 void Feed::appendArticles(const RSS::Document &doc) 00440 { 00441 bool changed = false; 00442 00443 RSS::Article::List d_articles = doc.articles(); 00444 RSS::Article::List::ConstIterator it; 00445 RSS::Article::List::ConstIterator en = d_articles.end(); 00446 00447 int nudge=0; 00448 00449 TQValueList<Article> deletedArticles = d->deletedArticles; 00450 00451 for (it = d_articles.begin(); it != en; ++it) 00452 { 00453 if ( !d->articles.contains((*it).guid()) ) // article not in list 00454 { 00455 Article mya(*it, this); 00456 mya.offsetPubDate(nudge); 00457 nudge--; 00458 appendArticle(mya); 00459 00460 TQValueList<ArticleInterceptor*> interceptors = ArticleInterceptorManager::self()->interceptors(); 00461 for (TQValueList<ArticleInterceptor*>::ConstIterator it = interceptors.begin(); it != interceptors.end(); ++it) 00462 (*it)->processArticle(mya); 00463 00464 d->addedArticlesNotify.append(mya); 00465 00466 if (!mya.isDeleted() && !markImmediatelyAsRead()) 00467 mya.setStatus(Article::New); 00468 else 00469 mya.setStatus(Article::Read); 00470 00471 changed = true; 00472 } 00473 else // article is in list 00474 { 00475 // 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. 00476 Article old = d->articles[(*it).guid()]; 00477 Article mya(*it, this); 00478 if (!mya.guidIsHash() && mya.hash() != old.hash() && !old.isDeleted()) 00479 { 00480 mya.setKeep(old.keep()); 00481 int oldstatus = old.status(); 00482 old.setStatus(Article::Read); 00483 00484 d->articles.remove(old.guid()); 00485 appendArticle(mya); 00486 00487 mya.setStatus(oldstatus); 00488 00489 d->updatedArticlesNotify.append(mya); 00490 changed = true; 00491 } 00492 else if (old.isDeleted()) 00493 deletedArticles.remove(mya); 00494 } 00495 } 00496 00497 TQValueList<Article>::ConstIterator dit = deletedArticles.begin(); 00498 TQValueList<Article>::ConstIterator dtmp; 00499 TQValueList<Article>::ConstIterator den = deletedArticles.end(); 00500 00501 // delete articles with delete flag set completely from archive, which aren't in the current feed source anymore 00502 while (dit != den) 00503 { 00504 dtmp = dit; 00505 ++dit; 00506 d->articles.remove((*dtmp).guid()); 00507 d->archive->deleteArticle((*dtmp).guid()); 00508 d->deletedArticles.remove(*dtmp); 00509 } 00510 00511 if (changed) 00512 articlesModified(); 00513 } 00514 00515 bool Feed::usesExpiryByAge() const 00516 { 00517 return ( d->archiveMode == globalDefault && Settings::archiveMode() == Settings::EnumArchiveMode::limitArticleAge) || d->archiveMode == limitArticleAge; 00518 } 00519 00520 bool Feed::isExpired(const Article& a) const 00521 { 00522 TQDateTime now = TQDateTime::currentDateTime(); 00523 int expiryAge = -1; 00524 // check whether the feed uses the global default and the default is limitArticleAge 00525 if ( d->archiveMode == globalDefault && Settings::archiveMode() == Settings::EnumArchiveMode::limitArticleAge) 00526 expiryAge = Settings::maxArticleAge() *24*3600; 00527 else // otherwise check if this feed has limitArticleAge set 00528 if ( d->archiveMode == limitArticleAge) 00529 expiryAge = d->maxArticleAge *24*3600; 00530 00531 return ( expiryAge != -1 && a.pubDate().secsTo(now) > expiryAge); 00532 } 00533 00534 void Feed::appendArticle(const Article& a) 00535 { 00536 if ( (a.keep() && Settings::doNotExpireImportantArticles()) || ( !usesExpiryByAge() || !isExpired(a) ) ) // if not expired 00537 { 00538 if (!d->articles.contains(a.guid())) 00539 { 00540 d->articles[a.guid()] = a; 00541 if (!a.isDeleted() && a.status() != Article::Read) 00542 setUnread(unread()+1); 00543 } 00544 } 00545 } 00546 00547 00548 void Feed::fetch(bool followDiscovery) 00549 { 00550 d->followDiscovery = followDiscovery; 00551 d->fetchTries = 0; 00552 00553 // mark all new as unread 00554 TQValueList<Article> articles = d->articles.values(); 00555 TQValueList<Article>::Iterator it; 00556 TQValueList<Article>::Iterator en = articles.end(); 00557 for (it = articles.begin(); it != en; ++it) 00558 { 00559 if ((*it).status() == Article::New) 00560 { 00561 (*it).setStatus(Article::Unread); 00562 } 00563 } 00564 00565 emit fetchStarted(this); 00566 00567 tryFetch(); 00568 } 00569 00570 void Feed::slotAbortFetch() 00571 { 00572 if (d->loader) 00573 { 00574 d->loader->abort(); 00575 } 00576 } 00577 00578 void Feed::tryFetch() 00579 { 00580 d->fetchError = false; 00581 00582 d->loader = RSS::Loader::create( this, TQT_SLOT(fetchCompleted(Loader *, Document, Status)) ); 00583 //connect(d->loader, TQT_SIGNAL(progress(unsigned long)), this, TQT_SLOT(slotSetProgress(unsigned long))); 00584 d->loader->loadFrom( d->xmlUrl, new RSS::FileRetriever ); 00585 } 00586 00587 void Feed::slotImageFetched(const TQPixmap& image) 00588 { 00589 if (image.isNull()) 00590 return; 00591 d->imagePixmap=image; 00592 d->imagePixmap.save(TDEGlobal::dirs()->saveLocation("cache", "akregator/Media/") 00593 + Utils::fileNameForUrl(d->xmlUrl) + 00594 ".png","PNG"); 00595 nodeModified(); 00596 } 00597 00598 void Feed::fetchCompleted(RSS::Loader *l, RSS::Document doc, RSS::Status status) 00599 { 00600 // Note that loader instances delete themselves 00601 d->loader = 0; 00602 00603 // fetching wasn't successful: 00604 if (status != RSS::Success) 00605 { 00606 if (status == RSS::Aborted) 00607 { 00608 d->fetchError = false; 00609 emit fetchAborted(this); 00610 } 00611 else if (d->followDiscovery && (status == RSS::ParseError) && (d->fetchTries < 3) && (l->discoveredFeedURL().isValid())) 00612 { 00613 d->fetchTries++; 00614 d->xmlUrl = l->discoveredFeedURL().url(); 00615 emit fetchDiscovery(this); 00616 tryFetch(); 00617 } 00618 else 00619 { 00620 d->fetchError = true; 00621 d->lastErrorFetch = TQDateTime::currentDateTime().toTime_t(); 00622 emit fetchError(this); 00623 } 00624 return; 00625 } 00626 00627 loadArticles(); // TODO: make me fly: make this delayed 00628 00629 // Restore favicon. 00630 if (d->favicon.isNull()) 00631 loadFavicon(); 00632 00633 d->fetchError = false; 00634 00635 if (doc.image() && d->imagePixmap.isNull()) 00636 { 00637 d->image = *doc.image(); 00638 connect(&d->image, TQT_SIGNAL(gotPixmap(const TQPixmap&)), this, TQT_SLOT(slotImageFetched(const TQPixmap&))); 00639 d->image.getPixmap(); 00640 } 00641 00642 if (title().isEmpty()) 00643 setTitle( doc.title() ); 00644 00645 d->description = doc.description(); 00646 d->htmlUrl = doc.link().url(); 00647 00648 appendArticles(doc); 00649 00650 d->archive->setLastFetch( TQDateTime::currentDateTime().toTime_t()); 00651 emit fetched(this); 00652 } 00653 00654 void Feed::loadFavicon() 00655 { 00656 FeedIconManager::self()->fetchIcon(this); 00657 } 00658 00659 void Feed::slotDeleteExpiredArticles() 00660 { 00661 if ( !usesExpiryByAge() ) 00662 return; 00663 00664 TQValueList<Article> articles = d->articles.values(); 00665 00666 TQValueList<Article>::Iterator en = articles.end(); 00667 00668 setNotificationMode(false); 00669 00670 // check keep flag only if it should be respected for expiry 00671 // the code could be more compact, but we better check 00672 // doNotExpiredArticles once instead of in every iteration 00673 if (Settings::doNotExpireImportantArticles()) 00674 { 00675 for (TQValueList<Article>::Iterator it = articles.begin(); it != en; ++it) 00676 { 00677 if (!(*it).keep() && isExpired(*it)) 00678 { 00679 (*it).setDeleted(); 00680 } 00681 } 00682 } 00683 else 00684 { 00685 for (TQValueList<Article>::Iterator it = articles.begin(); it != en; ++it) 00686 { 00687 if (isExpired(*it)) 00688 { 00689 (*it).setDeleted(); 00690 } 00691 } 00692 } 00693 setNotificationMode(true); 00694 } 00695 00696 void Feed::setFavicon(const TQPixmap &p) 00697 { 00698 d->favicon = p; 00699 nodeModified(); 00700 } 00701 00702 Feed::ArchiveMode Feed::archiveMode() const 00703 { 00704 return d->archiveMode; 00705 } 00706 00707 void Feed::setArchiveMode(ArchiveMode archiveMode) 00708 { 00709 d->archiveMode = archiveMode; 00710 } 00711 00712 int Feed::unread() const 00713 { 00714 return d->archive ? d->archive->unread() : 0; 00715 } 00716 00717 void Feed::setUnread(int unread) 00718 { 00719 if (d->archive && unread != d->archive->unread()) 00720 { 00721 d->archive->setUnread(unread); 00722 nodeModified(); 00723 } 00724 } 00725 00726 00727 void Feed::setArticleDeleted(Article& a) 00728 { 00729 if (!d->deletedArticles.contains(a)) 00730 d->deletedArticles.append(a); 00731 00732 if (!d->removedArticlesNotify.contains(a)) 00733 d->removedArticlesNotify.append(a); 00734 00735 articlesModified(); 00736 } 00737 00738 void Feed::setArticleChanged(Article& a, int oldStatus) 00739 { 00740 if (oldStatus != -1) 00741 { 00742 int newStatus = a.status(); 00743 if (oldStatus == Article::Read && newStatus != Article::Read) 00744 setUnread(unread()+1); 00745 else if (oldStatus != Article::Read && newStatus == Article::Read) 00746 setUnread(unread()-1); 00747 } 00748 d->updatedArticlesNotify.append(a); 00749 articlesModified(); 00750 } 00751 00752 int Feed::totalCount() const 00753 { 00754 return d->articles.count(); 00755 } 00756 00757 TreeNode* Feed::next() 00758 { 00759 if ( nextSibling() ) 00760 return nextSibling(); 00761 00762 Folder* p = parent(); 00763 while (p) 00764 { 00765 if ( p->nextSibling() ) 00766 return p->nextSibling(); 00767 else 00768 p = p->parent(); 00769 } 00770 return 0; 00771 } 00772 00773 void Feed::doArticleNotification() 00774 { 00775 if (!d->addedArticlesNotify.isEmpty()) 00776 { 00777 // copy list, otherwise the refcounting in Article::Private breaks for 00778 // some reason (causing segfaults) 00779 TQValueList<Article> l = d->addedArticlesNotify; 00780 emit signalArticlesAdded(this, l); 00781 d->addedArticlesNotify.clear(); 00782 } 00783 if (!d->updatedArticlesNotify.isEmpty()) 00784 { 00785 // copy list, otherwise the refcounting in Article::Private breaks for 00786 // some reason (causing segfaults) 00787 TQValueList<Article> l = d->updatedArticlesNotify; 00788 emit signalArticlesUpdated(this, l); 00789 d->updatedArticlesNotify.clear(); 00790 } 00791 if (!d->removedArticlesNotify.isEmpty()) 00792 { 00793 // copy list, otherwise the refcounting in Article::Private breaks for 00794 // some reason (causing segfaults) 00795 TQValueList<Article> l = d->removedArticlesNotify; 00796 emit signalArticlesRemoved(this, l); 00797 d->removedArticlesNotify.clear(); 00798 } 00799 TreeNode::doArticleNotification(); 00800 } 00801 00802 void Feed::enforceLimitArticleNumber() 00803 { 00804 int limit = -1; 00805 if (d->archiveMode == globalDefault && Settings::archiveMode() == Settings::EnumArchiveMode::limitArticleNumber) 00806 limit = Settings::maxArticleNumber(); 00807 else if (d->archiveMode == limitArticleNumber) 00808 limit = maxArticleNumber(); 00809 00810 if (limit == -1 || limit >= d->articles.count() - d->deletedArticles.count()) 00811 return; 00812 00813 setNotificationMode(false); 00814 TQValueList<Article> articles = d->articles.values(); 00815 qHeapSort(articles); 00816 TQValueList<Article>::Iterator it = articles.begin(); 00817 TQValueList<Article>::Iterator tmp; 00818 TQValueList<Article>::Iterator en = articles.end(); 00819 00820 int c = 0; 00821 00822 if (Settings::doNotExpireImportantArticles()) 00823 { 00824 while (it != en) 00825 { 00826 tmp = it; 00827 ++it; 00828 if (c < limit) 00829 { 00830 if (!(*tmp).isDeleted() && !(*tmp).keep()) 00831 c++; 00832 } 00833 else if (!(*tmp).keep()) 00834 (*tmp).setDeleted(); 00835 } 00836 } 00837 else 00838 { 00839 while (it != en) 00840 { 00841 tmp = it; 00842 ++it; 00843 if (c < limit && !(*tmp).isDeleted()) 00844 { 00845 c++; 00846 } 00847 else 00848 { 00849 (*tmp).setDeleted(); 00850 } 00851 } 00852 } 00853 setNotificationMode(true); 00854 } 00855 00856 } // namespace Akregator 00857 #include "feed.moc"