akregator/src

feed.cpp
1 /*
2  This file is part of Akregator.
3 
4  Copyright (C) 2004 Stanislav Karchebny <Stanislav.Karchebny@kdemail.net>
5  2005 Frank Osterfeld <frank.osterfeld at kdemail.net>
6 
7  This program is free software; you can redistribute it and/or modify
8  it under the terms of the GNU General Public License as published by
9  the Free Software Foundation; either version 2 of the License, or
10  (at your option) any later version.
11 
12  This program is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  GNU General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with this program; if not, write to the Free Software
19  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 
21  As a special exception, permission is given to link this program
22  with any edition of TQt, and distribute the resulting executable,
23  without including the source code for TQt in the source distribution.
24 */
25 
26 #include <tqtimer.h>
27 #include <tqdatetime.h>
28 #include <tqlistview.h>
29 #include <tqdom.h>
30 #include <tqmap.h>
31 #include <tqpixmap.h>
32 #include <tqvaluelist.h>
33 
34 #include <kurl.h>
35 #include <kdebug.h>
36 #include <tdeglobal.h>
37 #include <kstandarddirs.h>
38 
39 #include "akregatorconfig.h"
40 #include "article.h"
41 #include "articleinterceptor.h"
42 #include "feed.h"
43 #include "folder.h"
44 #include "fetchqueue.h"
45 #include "feediconmanager.h"
46 #include "feedstorage.h"
47 #include "storage.h"
48 #include "treenodevisitor.h"
49 #include "utils.h"
50 
51 #include "librss/librss.h"
52 
53 namespace Akregator {
54 
55 class Feed::FeedPrivate
56 {
57  public:
58  bool autoFetch;
59  int fetchInterval;
61  int maxArticleAge;
62  int maxArticleNumber;
64  bool useNotification;
65  bool loadLinkedWebsite;
66 
67  bool fetchError;
68 
69  int lastErrorFetch; // save time of last fetch that went wrong.
70  // != lastFetch property from the archive
71  // (that saves the last _successfull fetch!)
72  // workaround for 3.5.x
73 
74  int fetchTries;
75  bool followDiscovery;
76  RSS::Loader* loader;
77  bool articlesLoaded;
78  Backend::FeedStorage* archive;
79 
80  TQString xmlUrl;
81  TQString htmlUrl;
82  TQString description;
83 
85  TQMap<TQString, Article> articles;
86 
88  TQMap<TQString, TQStringList> taggedArticles;
89 
91  TQValueList<Article> deletedArticles;
92 
95  TQValueList<Article> addedArticlesNotify;
96  TQValueList<Article> removedArticlesNotify;
97  TQValueList<Article> updatedArticlesNotify;
98 
99  TQPixmap imagePixmap;
100  RSS::Image image;
101  TQPixmap favicon;
102 };
103 
105 {
106  switch (mode)
107  {
108  case keepAllArticles:
109  return "keepAllArticles";
110  case disableArchiving:
111  return "disableArchiving";
112  case limitArticleNumber:
113  return "limitArticleNumber";
114  case limitArticleAge:
115  return "limitArticleAge";
116  default:
117  return "globalDefault";
118  }
119 
120  // in a perfect world, this is never reached
121 
122  return "globalDefault";
123 }
124 
125 Feed* Feed::fromOPML(TQDomElement e)
126 {
127 
128  Feed* feed = 0;
129 
130  if( e.hasAttribute("xmlUrl") || e.hasAttribute("xmlurl") || e.hasAttribute("xmlURL") )
131  {
132  TQString title = e.hasAttribute("text") ? e.attribute("text") : e.attribute("title");
133 
134  TQString xmlUrl = e.hasAttribute("xmlUrl") ? e.attribute("xmlUrl") : e.attribute("xmlurl");
135  if (xmlUrl.isEmpty())
136  xmlUrl = e.attribute("xmlURL");
137 
138  bool useCustomFetchInterval = e.attribute("useCustomFetchInterval") == "true" || e.attribute("autoFetch") == "true";
139  // "autoFetch" is used in 3.4
140  // Will be removed in KDE4
141 
142  TQString htmlUrl = e.attribute("htmlUrl");
143  TQString description = e.attribute("description");
144  int fetchInterval = e.attribute("fetchInterval").toInt();
145  ArchiveMode archiveMode = stringToArchiveMode(e.attribute("archiveMode"));
146  int maxArticleAge = e.attribute("maxArticleAge").toUInt();
147  int maxArticleNumber = e.attribute("maxArticleNumber").toUInt();
148  bool markImmediatelyAsRead = e.attribute("markImmediatelyAsRead") == "true";
149  bool useNotification = e.attribute("useNotification") == "true";
150  bool loadLinkedWebsite = e.attribute("loadLinkedWebsite") == "true";
151  uint id = e.attribute("id").toUInt();
152 
153  feed = new Feed();
154  feed->setTitle(title);
155  feed->setXmlUrl(xmlUrl);
157  feed->setHtmlUrl(htmlUrl);
158  feed->setId(id);
161  feed->setUseNotification(useNotification);
165  feed->setMarkImmediatelyAsRead(markImmediatelyAsRead);
166  feed->setLoadLinkedWebsite(loadLinkedWebsite);
167  feed->loadArticles(); // TODO: make me fly: make this delayed
168  feed->loadImage();
169  }
170 
171  return feed;
172 }
173 
174 bool Feed::accept(TreeNodeVisitor* visitor)
175 {
176  if (visitor->visitFeed(this))
177  return true;
178  else
179  return visitor->visitTreeNode(this);
180 }
181 
182 TQStringList Feed::tags() const
183 {
184  return d->archive->tags();
185 }
186 
187 Article Feed::findArticle(const TQString& guid) const
188 {
189  return d->articles[guid];
190 }
191 
192 TQValueList<Article> Feed::articles(const TQString& tag)
193 {
194  if (!d->articlesLoaded)
195  loadArticles();
196  if (tag.isNull())
197  return d->articles.values();
198  else
199  {
200  TQValueList<Article> tagged;
201  TQStringList guids = d->archive->articles(tag);
202  for (TQStringList::ConstIterator it = guids.begin(); it != guids.end(); ++it)
203  tagged += d->articles[*it];
204  return tagged;
205 
206  }
207 }
208 
210 {
211  TQString imageFileName = TDEGlobal::dirs()->saveLocation("cache", "akregator/Media/")
212  + Utils::fileNameForUrl(d->xmlUrl) +
213 ".png";
214  d->imagePixmap.load(imageFileName, "PNG");
215 }
216 
218 {
219  if (d->articlesLoaded)
220  return;
221 
222  if (!d->archive)
223  d->archive = Backend::Storage::getInstance()->archiveFor(xmlUrl());
224 
225  TQStringList list = d->archive->articles();
226  for ( TQStringList::ConstIterator it = list.begin(); it != list.end(); ++it)
227  {
228  Article mya(*it, this);
229  d->articles[mya.guid()] = mya;
230  if (mya.isDeleted())
231  d->deletedArticles.append(mya);
232  }
233 
234  d->articlesLoaded = true;
235  enforceLimitArticleNumber();
236  recalcUnreadCount();
237 }
238 
239 void Feed::recalcUnreadCount()
240 {
241  TQValueList<Article> tarticles = articles();
242  TQValueList<Article>::Iterator it;
243  TQValueList<Article>::Iterator en = tarticles.end();
244 
245  int oldUnread = d->archive->unread();
246 
247  int unread = 0;
248 
249  for (it = tarticles.begin(); it != en; ++it)
250  if (!(*it).isDeleted() && (*it).status() != Article::Read)
251  ++unread;
252 
253  if (unread != oldUnread)
254  {
255  d->archive->setUnread(unread);
256  nodeModified();
257  }
258 }
259 
261 {
262  if (str == "globalDefault")
263  return globalDefault;
264  if (str == "keepAllArticles")
265  return keepAllArticles;
266  if (str == "disableArchiving")
267  return disableArchiving;
268  if (str == "limitArticleNumber")
269  return limitArticleNumber;
270  if (str == "limitArticleAge")
271  return limitArticleAge;
272 
273  return globalDefault;
274 }
275 
276 Feed::Feed() : TreeNode(), d(new FeedPrivate)
277 {
278  d->autoFetch = false;
279  d->fetchInterval = 30;
280  d->archiveMode = globalDefault;
281  d->maxArticleAge = 60;
282  d->maxArticleNumber = 1000;
283  d->markImmediatelyAsRead = false;
284  d->useNotification = false;
285  d->fetchError = false;
286  d->lastErrorFetch = 0;
287  d->fetchTries = 0;
288  d->loader = 0;
289  d->articlesLoaded = false;
290  d->archive = 0;
291  d->loadLinkedWebsite = false;
292 }
293 
294 Feed::~Feed()
295 {
296  slotAbortFetch();
297  emitSignalDestroyed();
298  delete d;
299  d = 0;
300 }
301 
302 bool Feed::useCustomFetchInterval() const { return d->autoFetch; }
303 
304 void Feed::setCustomFetchIntervalEnabled(bool enabled) { d->autoFetch = enabled; }
305 
306 int Feed::fetchInterval() const { return d->fetchInterval; }
307 
308 void Feed::setFetchInterval(int interval) { d->fetchInterval = interval; }
309 
310 int Feed::maxArticleAge() const { return d->maxArticleAge; }
311 
312 void Feed::setMaxArticleAge(int maxArticleAge) { d->maxArticleAge = maxArticleAge; }
313 
314 int Feed::maxArticleNumber() const { return d->maxArticleNumber; }
315 
316 void Feed::setMaxArticleNumber(int maxArticleNumber) { d->maxArticleNumber = maxArticleNumber; }
317 
318 bool Feed::markImmediatelyAsRead() const { return d->markImmediatelyAsRead; }
319 
320 void Feed::setMarkImmediatelyAsRead(bool enabled)
321 {
322  d->markImmediatelyAsRead = enabled;
323  if (enabled)
325 }
326 
327 void Feed::setUseNotification(bool enabled)
328 {
329  d->useNotification = enabled;
330 }
331 
332 bool Feed::useNotification() const
333 {
334  return d->useNotification;
335 }
336 
337 void Feed::setLoadLinkedWebsite(bool enabled)
338 {
339  d->loadLinkedWebsite = enabled;
340 }
341 
342 bool Feed::loadLinkedWebsite() const
343 {
344  return d->loadLinkedWebsite;
345 }
346 
347 const TQPixmap& Feed::favicon() const { return d->favicon; }
348 
349 const TQPixmap& Feed::image() const { return d->imagePixmap; }
350 
351 const TQString& Feed::xmlUrl() const { return d->xmlUrl; }
352 
353 void Feed::setXmlUrl(const TQString& s) { d->xmlUrl = s; }
354 
355 const TQString& Feed::htmlUrl() const { return d->htmlUrl; }
356 
357 void Feed::setHtmlUrl(const TQString& s) { d->htmlUrl = s; }
358 
359 const TQString& Feed::description() const { return d->description; }
360 
361 void Feed::setDescription(const TQString& s) { d->description = s; }
362 
363 bool Feed::fetchErrorOccurred() { return d->fetchError; }
364 
365 bool Feed::isArticlesLoaded() const { return d->articlesLoaded; }
366 
367 
368 TQDomElement Feed::toOPML( TQDomElement parent, TQDomDocument document ) const
369 {
370  TQDomElement el = document.createElement( "outline" );
371  el.setAttribute( "text", title() );
372  el.setAttribute( "title", title() );
373  el.setAttribute( "xmlUrl", d->xmlUrl );
374  el.setAttribute( "htmlUrl", d->htmlUrl );
375  el.setAttribute( "id", TQString::number(id()) );
376  el.setAttribute( "description", d->description );
377  el.setAttribute( "useCustomFetchInterval", (useCustomFetchInterval() ? "true" : "false") );
378  el.setAttribute( "fetchInterval", TQString::number(fetchInterval()) );
379  el.setAttribute( "archiveMode", archiveModeToString(d->archiveMode) );
380  el.setAttribute( "maxArticleAge", d->maxArticleAge );
381  el.setAttribute( "maxArticleNumber", d->maxArticleNumber );
382  if (d->markImmediatelyAsRead)
383  el.setAttribute( "markImmediatelyAsRead", "true" );
384  if (d->useNotification)
385  el.setAttribute( "useNotification", "true" );
386  if (d->loadLinkedWebsite)
387  el.setAttribute( "loadLinkedWebsite", "true" );
388  el.setAttribute( "maxArticleNumber", d->maxArticleNumber );
389  el.setAttribute( "type", "rss" ); // despite some additional fields, its still "rss" OPML
390  el.setAttribute( "version", "RSS" );
391  parent.appendChild( el );
392  return el;
393 }
394 
396 {
397  if (unread() > 0)
398  {
399  setNotificationMode(false, true);
400  TQValueList<Article> tarticles = articles();
401  TQValueList<Article>::Iterator it;
402  TQValueList<Article>::Iterator en = tarticles.end();
403 
404  for (it = tarticles.begin(); it != en; ++it)
405  (*it).setStatus(Article::Read);
406  setNotificationMode(true, true);
407  }
408 }
409 void Feed::slotAddToFetchQueue(FetchQueue* queue, bool intervalFetchOnly)
410 {
411  if (!intervalFetchOnly)
412  queue->addFeed(this);
413  else
414  {
415  uint now = TQDateTime::currentDateTime().toTime_t();
416 
417  // workaround for 3.5.x: if the last fetch went wrong, try again after 30 minutes
418  // this fixes annoying behaviour of akregator, especially when the host is reachable
419  // but Akregator can't parse the feed (the host is hammered every minute then)
420  if ( fetchErrorOccurred() && now - d->lastErrorFetch <= 30*60 )
421  return;
422 
423  int interval = -1;
424 
425  if (useCustomFetchInterval() )
426  interval = fetchInterval() * 60;
427  else
428  if ( Settings::useIntervalFetch() )
429  interval = Settings::autoFetchInterval() * 60;
430 
431  uint lastFetch = d->archive->lastFetch();
432 
433  if ( interval > 0 && now - lastFetch >= (uint)interval )
434  queue->addFeed(this);
435  }
436 }
437 
438 
439 void Feed::appendArticles(const RSS::Document &doc)
440 {
441  bool changed = false;
442 
443  RSS::Article::List d_articles = doc.articles();
444  RSS::Article::List::ConstIterator it;
445  RSS::Article::List::ConstIterator en = d_articles.end();
446 
447  int nudge=0;
448 
449  TQValueList<Article> deletedArticles = d->deletedArticles;
450 
451  for (it = d_articles.begin(); it != en; ++it)
452  {
453  if ( !d->articles.contains((*it).guid()) ) // article not in list
454  {
455  Article mya(*it, this);
456  mya.offsetPubDate(nudge);
457  nudge--;
458  appendArticle(mya);
459 
460  TQValueList<ArticleInterceptor*> interceptors = ArticleInterceptorManager::self()->interceptors();
461  for (TQValueList<ArticleInterceptor*>::ConstIterator it = interceptors.begin(); it != interceptors.end(); ++it)
462  (*it)->processArticle(mya);
463 
464  d->addedArticlesNotify.append(mya);
465 
466  if (!mya.isDeleted() && !markImmediatelyAsRead())
467  mya.setStatus(Article::New);
468  else
469  mya.setStatus(Article::Read);
470 
471  changed = true;
472  }
473  else // article is in list
474  {
475  // 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.
476  Article old = d->articles[(*it).guid()];
477  Article mya(*it, this);
478  if (!mya.guidIsHash() && mya.hash() != old.hash() && !old.isDeleted())
479  {
480  mya.setKeep(old.keep());
481  int oldstatus = old.status();
482  old.setStatus(Article::Read);
483 
484  d->articles.remove(old.guid());
485  appendArticle(mya);
486 
487  mya.setStatus(oldstatus);
488 
489  d->updatedArticlesNotify.append(mya);
490  changed = true;
491  }
492  else if (old.isDeleted())
493  deletedArticles.remove(mya);
494  }
495  }
496 
497  TQValueList<Article>::ConstIterator dit = deletedArticles.begin();
498  TQValueList<Article>::ConstIterator dtmp;
499  TQValueList<Article>::ConstIterator den = deletedArticles.end();
500 
501  // delete articles with delete flag set completely from archive, which aren't in the current feed source anymore
502  while (dit != den)
503  {
504  dtmp = dit;
505  ++dit;
506  d->articles.remove((*dtmp).guid());
507  d->archive->deleteArticle((*dtmp).guid());
508  d->deletedArticles.remove(*dtmp);
509  }
510 
511  if (changed)
513 }
514 
515 bool Feed::usesExpiryByAge() const
516 {
517  return ( d->archiveMode == globalDefault && Settings::archiveMode() == Settings::EnumArchiveMode::limitArticleAge) || d->archiveMode == limitArticleAge;
518 }
519 
520 bool Feed::isExpired(const Article& a) const
521 {
522  TQDateTime now = TQDateTime::currentDateTime();
523  int expiryAge = -1;
524 // check whether the feed uses the global default and the default is limitArticleAge
525  if ( d->archiveMode == globalDefault && Settings::archiveMode() == Settings::EnumArchiveMode::limitArticleAge)
526  expiryAge = Settings::maxArticleAge() *24*3600;
527  else // otherwise check if this feed has limitArticleAge set
528  if ( d->archiveMode == limitArticleAge)
529  expiryAge = d->maxArticleAge *24*3600;
530 
531  return ( expiryAge != -1 && a.pubDate().secsTo(now) > expiryAge);
532 }
533 
534 void Feed::appendArticle(const Article& a)
535 {
536  if ( (a.keep() && Settings::doNotExpireImportantArticles()) || ( !usesExpiryByAge() || !isExpired(a) ) ) // if not expired
537  {
538  if (!d->articles.contains(a.guid()))
539  {
540  d->articles[a.guid()] = a;
541  if (!a.isDeleted() && a.status() != Article::Read)
542  setUnread(unread()+1);
543  }
544  }
545 }
546 
547 
548 void Feed::fetch(bool followDiscovery)
549 {
550  d->followDiscovery = followDiscovery;
551  d->fetchTries = 0;
552 
553  // mark all new as unread
554  TQValueList<Article> articles = d->articles.values();
555  TQValueList<Article>::Iterator it;
556  TQValueList<Article>::Iterator en = articles.end();
557  for (it = articles.begin(); it != en; ++it)
558  {
559  if ((*it).status() == Article::New)
560  {
561  (*it).setStatus(Article::Unread);
562  }
563  }
564 
565  emit fetchStarted(this);
566 
567  tryFetch();
568 }
569 
570 void Feed::slotAbortFetch()
571 {
572  if (d->loader)
573  {
574  d->loader->abort();
575  }
576 }
577 
578 void Feed::tryFetch()
579 {
580  d->fetchError = false;
581 
582  d->loader = RSS::Loader::create( this, TQ_SLOT(fetchCompleted(Loader *, Document, Status)) );
583  //connect(d->loader, TQ_SIGNAL(progress(unsigned long)), this, TQ_SLOT(slotSetProgress(unsigned long)));
584  d->loader->loadFrom( d->xmlUrl, new RSS::FileRetriever );
585 }
586 
587 void Feed::slotImageFetched(const TQPixmap& image)
588 {
589  if (image.isNull())
590  return;
591  d->imagePixmap=image;
592  d->imagePixmap.save(TDEGlobal::dirs()->saveLocation("cache", "akregator/Media/")
593  + Utils::fileNameForUrl(d->xmlUrl) +
594 ".png","PNG");
595  nodeModified();
596 }
597 
598 void Feed::fetchCompleted(RSS::Loader *l, RSS::Document doc, RSS::Status status)
599 {
600  // Note that loader instances delete themselves
601  d->loader = 0;
602 
603  // fetching wasn't successful:
604  if (status != RSS::Success)
605  {
606  if (status == RSS::Aborted)
607  {
608  d->fetchError = false;
609  emit fetchAborted(this);
610  }
611  else if (d->followDiscovery && (status == RSS::ParseError) && (d->fetchTries < 3) && (l->discoveredFeedURL().isValid()))
612  {
613  d->fetchTries++;
614  d->xmlUrl = l->discoveredFeedURL().url();
615  emit fetchDiscovery(this);
616  tryFetch();
617  }
618  else
619  {
620  d->fetchError = true;
621  d->lastErrorFetch = TQDateTime::currentDateTime().toTime_t();
622  emit fetchError(this);
623  }
624  return;
625  }
626 
627  loadArticles(); // TODO: make me fly: make this delayed
628 
629  // Restore favicon.
630  if (d->favicon.isNull())
631  loadFavicon();
632 
633  d->fetchError = false;
634 
635  if (doc.image() && d->imagePixmap.isNull())
636  {
637  d->image = *doc.image();
638  connect(&d->image, TQ_SIGNAL(gotPixmap(const TQPixmap&)), this, TQ_SLOT(slotImageFetched(const TQPixmap&)));
639  d->image.getPixmap();
640  }
641 
642  if (title().isEmpty())
643  setTitle( doc.title() );
644 
645  d->description = doc.description();
646  d->htmlUrl = doc.link().url();
647 
648  appendArticles(doc);
649 
650  d->archive->setLastFetch( TQDateTime::currentDateTime().toTime_t());
651  emit fetched(this);
652 }
653 
655 {
656  FeedIconManager::self()->fetchIcon(this);
657 }
658 
660 {
661  if ( !usesExpiryByAge() )
662  return;
663 
664  TQValueList<Article> articles = d->articles.values();
665 
666  TQValueList<Article>::Iterator en = articles.end();
667 
668  setNotificationMode(false);
669 
670  // check keep flag only if it should be respected for expiry
671  // the code could be more compact, but we better check
672  // doNotExpiredArticles once instead of in every iteration
673  if (Settings::doNotExpireImportantArticles())
674  {
675  for (TQValueList<Article>::Iterator it = articles.begin(); it != en; ++it)
676  {
677  if (!(*it).keep() && isExpired(*it))
678  {
679  (*it).setDeleted();
680  }
681  }
682  }
683  else
684  {
685  for (TQValueList<Article>::Iterator it = articles.begin(); it != en; ++it)
686  {
687  if (isExpired(*it))
688  {
689  (*it).setDeleted();
690  }
691  }
692  }
693  setNotificationMode(true);
694 }
695 
696 void Feed::setFavicon(const TQPixmap &p)
697 {
698  d->favicon = p;
699  nodeModified();
700 }
701 
703 {
704  return d->archiveMode;
705 }
706 
708 {
709  d->archiveMode = archiveMode;
710 }
711 
712 int Feed::unread() const
713 {
714  return d->archive ? d->archive->unread() : 0;
715 }
716 
717 void Feed::setUnread(int unread)
718 {
719  if (d->archive && unread != d->archive->unread())
720  {
721  d->archive->setUnread(unread);
722  nodeModified();
723  }
724 }
725 
726 
727 void Feed::setArticleDeleted(Article& a)
728 {
729  if (!d->deletedArticles.contains(a))
730  d->deletedArticles.append(a);
731 
732  if (!d->removedArticlesNotify.contains(a))
733  d->removedArticlesNotify.append(a);
734 
736 }
737 
738 void Feed::setArticleChanged(Article& a, int oldStatus)
739 {
740  if (oldStatus != -1)
741  {
742  int newStatus = a.status();
743  if (oldStatus == Article::Read && newStatus != Article::Read)
744  setUnread(unread()+1);
745  else if (oldStatus != Article::Read && newStatus == Article::Read)
746  setUnread(unread()-1);
747  }
748  d->updatedArticlesNotify.append(a);
750 }
751 
752 int Feed::totalCount() const
753 {
754  return d->articles.count();
755 }
756 
758 {
759  if ( nextSibling() )
760  return nextSibling();
761 
762  Folder* p = parent();
763  while (p)
764  {
765  if ( p->nextSibling() )
766  return p->nextSibling();
767  else
768  p = p->parent();
769  }
770  return 0;
771 }
772 
774 {
775  if (!d->addedArticlesNotify.isEmpty())
776  {
777  // copy list, otherwise the refcounting in Article::Private breaks for
778  // some reason (causing segfaults)
779  TQValueList<Article> l = d->addedArticlesNotify;
780  emit signalArticlesAdded(this, l);
781  d->addedArticlesNotify.clear();
782  }
783  if (!d->updatedArticlesNotify.isEmpty())
784  {
785  // copy list, otherwise the refcounting in Article::Private breaks for
786  // some reason (causing segfaults)
787  TQValueList<Article> l = d->updatedArticlesNotify;
788  emit signalArticlesUpdated(this, l);
789  d->updatedArticlesNotify.clear();
790  }
791  if (!d->removedArticlesNotify.isEmpty())
792  {
793  // copy list, otherwise the refcounting in Article::Private breaks for
794  // some reason (causing segfaults)
795  TQValueList<Article> l = d->removedArticlesNotify;
796  emit signalArticlesRemoved(this, l);
797  d->removedArticlesNotify.clear();
798  }
800 }
801 
802 void Feed::enforceLimitArticleNumber()
803 {
804  int limit = -1;
805  if (d->archiveMode == globalDefault && Settings::archiveMode() == Settings::EnumArchiveMode::limitArticleNumber)
806  limit = Settings::maxArticleNumber();
807  else if (d->archiveMode == limitArticleNumber)
808  limit = maxArticleNumber();
809 
810  if (limit == -1 || limit >= d->articles.count() - d->deletedArticles.count())
811  return;
812 
813  setNotificationMode(false);
814  TQValueList<Article> articles = d->articles.values();
815  qHeapSort(articles);
816  TQValueList<Article>::Iterator it = articles.begin();
817  TQValueList<Article>::Iterator tmp;
818  TQValueList<Article>::Iterator en = articles.end();
819 
820  int c = 0;
821 
822  if (Settings::doNotExpireImportantArticles())
823  {
824  while (it != en)
825  {
826  tmp = it;
827  ++it;
828  if (c < limit)
829  {
830  if (!(*tmp).isDeleted() && !(*tmp).keep())
831  c++;
832  }
833  else if (!(*tmp).keep())
834  (*tmp).setDeleted();
835  }
836  }
837  else
838  {
839  while (it != en)
840  {
841  tmp = it;
842  ++it;
843  if (c < limit && !(*tmp).isDeleted())
844  {
845  c++;
846  }
847  else
848  {
849  (*tmp).setDeleted();
850  }
851  }
852  }
853  setNotificationMode(true);
854 }
855 
856 } // namespace Akregator
857 #include "feed.moc"
A proxy class for RSS::Article with some additional methods to assist sorting.
Definition: article.h:58
represents a feed
Definition: feed.h:63
virtual int unread() const
returns the unread count for this feed
Definition: feed.cpp:712
void loadImage()
load the image from the cache if it is in there
Definition: feed.cpp:209
void setArchiveMode(ArchiveMode archiveMode)
sets the archiving mode for this feed
Definition: feed.cpp:707
virtual TQValueList< Article > articles(const TQString &tag=TQString())
Returns a sequence of the articles this node contains.
Definition: feed.cpp:192
void fetchError(Feed *)
emitted when a fetch error occurred
ArchiveMode
the archiving modes:
Definition: feed.h:76
bool markImmediatelyAsRead() const
if true, new articles are marked immediately as read instead of new/unread.
Definition: feed.cpp:318
void setDescription(const TQString &s)
sets the description of this feed
Definition: feed.cpp:361
bool fetchErrorOccurred()
returns whether a fetch error has occurred
Definition: feed.cpp:363
virtual TreeNode * next()
returns the next node in the tree.
Definition: feed.cpp:757
void loadFavicon()
downloads the favicon
Definition: feed.cpp:654
const TQPixmap & favicon() const
returns the favicon
Definition: feed.cpp:347
void loadArticles()
loads articles from archive
Definition: feed.cpp:217
int fetchInterval() const
Returns custom auto fetch interval of this feed.
Definition: feed.cpp:306
virtual int totalCount() const
returns the number of total articles in this feed
Definition: feed.cpp:752
void setLoadLinkedWebsite(bool enabled)
if true, the linked URL is loaded directly in the article viewer instead of showing the description
Definition: feed.cpp:337
void fetchStarted(Feed *)
emitted when fetching started
void fetchDiscovery(Feed *)
emitted when a feed URL was found by auto discovery
void setMaxArticleAge(int maxArticleAge)
sets the maximum age of articles used for expiration by age (used in limitArticleAge archive mode)
Definition: feed.cpp:312
const TQString & description() const
returns the description of this feed
Definition: feed.cpp:359
virtual void doArticleNotification()
reimplement this in subclasses to do the actual notification called by articlesModified
Definition: feed.cpp:773
virtual void slotAddToFetchQueue(FetchQueue *queue, bool intervalFetchOnly=false)
add this feed to the fetch queue queue
Definition: feed.cpp:409
void setMaxArticleNumber(int maxArticleNumber)
sets the article count limit used in limitArticleNumber archive mode
Definition: feed.cpp:316
static TQString archiveModeToString(ArchiveMode mode)
converts ArchiveMode values to corresponding strings
Definition: feed.cpp:104
int maxArticleAge() const
returns the maximum age of articles used for expiration by age (used in limitArticleAge archive mode)
Definition: feed.cpp:310
const TQPixmap & image() const
returns the feed image
Definition: feed.cpp:349
bool isArticlesLoaded() const
returns if the article archive of this feed is loaded
Definition: feed.cpp:365
Feed()
default constructor
Definition: feed.cpp:276
static Feed * fromOPML(TQDomElement e)
creates a Feed object from a description in OPML format
Definition: feed.cpp:125
const TQString & xmlUrl() const
returns the url of the actual feed source (rss/rdf/atom file)
Definition: feed.cpp:351
void setXmlUrl(const TQString &s)
sets the url of the actual feed source (rss/rdf/atom file)
Definition: feed.cpp:353
void setUnread(int unread)
sets the unread count for this feed
Definition: feed.cpp:717
bool useCustomFetchInterval() const
returns whether this feed uses its own fetch interval or the global setting
Definition: feed.cpp:302
virtual TQDomElement toOPML(TQDomElement parent, TQDomDocument document) const
exports the feed settings to OPML
Definition: feed.cpp:368
int maxArticleNumber() const
returns the article count limit used in limitArticleNumber archive mode
Definition: feed.cpp:314
virtual TQStringList tags() const
returns a list of all tags occurring in this node (sub tree for folders)
Definition: feed.cpp:182
void setFetchInterval(int interval)
Sets custom auto fetch interval.
Definition: feed.cpp:308
void fetchAborted(Feed *)
emitted when a fetch is aborted
void fetched(Feed *)
emitted when feed finished fetching
ArchiveMode archiveMode() const
returns the archiving mode which is used for this feed
Definition: feed.cpp:702
virtual void slotDeleteExpiredArticles()
deletes expired articles
Definition: feed.cpp:659
void fetch(bool followDiscovery=false)
starts fetching
Definition: feed.cpp:548
virtual void slotMarkAllArticlesAsRead()
mark all articles in this feed as read
Definition: feed.cpp:395
static ArchiveMode stringToArchiveMode(const TQString &str)
converts strings to ArchiveMode value if parsing fails, it returns ArchiveMode::globalDefault
Definition: feed.cpp:260
const TQString & htmlUrl() const
returns the URL of the HTML page of this feed
Definition: feed.cpp:355
void setFavicon(const TQPixmap &p)
sets the favicon (used in the tree view)
Definition: feed.cpp:696
void setHtmlUrl(const TQString &s)
sets the URL of the HTML page of this feed
Definition: feed.cpp:357
virtual Article findArticle(const TQString &guid) const
returns the article with the given guid, or a null article if it not exists
Definition: feed.cpp:187
void setCustomFetchIntervalEnabled(bool enabled)
set if the feed has its custom fetch interval or uses the global setting
Definition: feed.cpp:304
Represents a folder (containing feeds and/or other folders)
Definition: folder.h:45
virtual void appendChild(TreeNode *node)
inserts node as last child
Definition: folder.cpp:168
Abstract base class for all kind of elements in the feed tree, like feeds and feed groups (and search...
Definition: treenode.h:52
virtual TreeNode * nextSibling() const
Get the next sibling.
Definition: treenode.cpp:92
virtual void setTitle(const TQString &title)
Sets the title of the node.
Definition: treenode.cpp:82
void signalArticlesAdded(TreeNode *node, const TQValueList< Article > &guids)
emitted when new articles were added to this node or any node in the subtree (for folders).
virtual void setNotificationMode(bool doNotify, bool notifyOccurredChanges=true)
Definition: treenode.cpp:125
void signalArticlesUpdated(TreeNode *, const TQValueList< Article > &guids)
emitted when articles were updated
virtual void doArticleNotification()
reimplement this in subclasses to do the actual notification called by articlesModified
Definition: treenode.cpp:171
virtual void articlesModified()
call this if the articles in the node were changed.
Definition: treenode.cpp:163
void signalArticlesRemoved(TreeNode *, const TQValueList< Article > &guids)
emitted when articles were removed from this subtree.
virtual void setId(uint id)
sets the ID
Definition: treenode.cpp:150
virtual const TQString & title() const
Get title of node.
Definition: treenode.cpp:77
virtual void nodeModified()
call this if you modified the actual node (title, unread count).
Definition: treenode.cpp:155
virtual Folder * parent() const
Returns the parent node.
Definition: treenode.cpp:115