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