akregator/src

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"