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 Qt, and distribute the resulting executable,
00023     without including the source code for Qt in the source distribution.
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; // save time of last fetch that went wrong.
00069                             // != lastFetch property from the archive 
00070                             // (that saves the last _successfull fetch!)
00071                             // workaround for 3.5.x
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    // in a perfect world, this is never reached
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         // "autoFetch" is used in 3.4
00137         // Will be removed in KDE4
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(); // TODO: make me fly: make this delayed
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" ); // despite some additional fields, its still "rss" OPML
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         // workaround for 3.5.x: if the last fetch went wrong, try again after 30 minutes
00414         // this fixes annoying behaviour of akregator, especially when the host is reachable
00415         // but Akregator can't parse the feed (the host is hammered every minute then)
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()) ) // article not in list
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 // article is in list
00470         {
00471             // 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.
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     // delete articles with delete flag set completely from archive, which aren't in the current feed source anymore
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 // check whether the feed uses the global default and the default is limitArticleAge
00521     if ( d->archiveMode == globalDefault && Settings::archiveMode() == Settings::EnumArchiveMode::limitArticleAge)
00522         expiryAge = Settings::maxArticleAge() *24*3600;
00523     else // otherwise check if this feed has limitArticleAge set
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) ) ) // if not expired
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     // mark all new as unread
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     //connect(d->loader, SIGNAL(progress(unsigned long)), this, SLOT(slotSetProgress(unsigned long)));
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     // Note that loader instances delete themselves
00596     d->loader = 0;
00597 
00598     // fetching wasn't successful:
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(); // TODO: make me fly: make this delayed
00623     
00624     // Restore favicon.
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     // check keep flag only if it should be respected for expiry
00666     // the code could be more compact, but we better check
00667     // doNotExpiredArticles once instead of in every iteration
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         // copy list, otherwise the refcounting in Article::Private breaks for 
00773         // some reason (causing segfaults)
00774         QValueList<Article> l = d->addedArticlesNotify;
00775         emit signalArticlesAdded(this, l);
00776         d->addedArticlesNotify.clear();
00777     }
00778     if (!d->updatedArticlesNotify.isEmpty())
00779     {
00780         // copy list, otherwise the refcounting in Article::Private breaks for
00781         // some reason (causing segfaults)
00782         QValueList<Article> l = d->updatedArticlesNotify;
00783         emit signalArticlesUpdated(this, l);
00784         d->updatedArticlesNotify.clear();
00785     }
00786     if (!d->removedArticlesNotify.isEmpty())
00787     {
00788         // copy list, otherwise the refcounting in Article::Private breaks for 
00789         // some reason (causing segfaults)
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 } // namespace Akregator
00852 #include "feed.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys