akregator/src

article.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     This program is free software; you can redistribute it and/or modify
00007     it under the terms of the GNU General Public License as published by
00008     the Free Software Foundation; either version 2 of the License, or
00009     (at your option) any later version.
00010 
00011     This program is distributed in the hope that it will be useful,
00012     but WITHOUT ANY WARRANTY; without even the implied warranty of
00013     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00014     GNU General Public License for more details.
00015 
00016     You should have received a copy of the GNU General Public License
00017     along with this program; if not, write to the Free Software
00018     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00019 
00020     As a special exception, permission is given to link this program
00021     with any edition of Qt, and distribute the resulting executable,
00022     without including the source code for Qt in the source distribution.
00023 */
00024 
00025 #include "article.h"
00026 #include "feed.h"
00027 #include "feedstorage.h"
00028 #include "storage.h"
00029 #include "librss/librss.h"
00030 #include "shared.h"
00031 #include "utils.h"
00032 
00033 #include <qdatetime.h>
00034 #include <qdom.h>
00035 #include <qregexp.h>
00036 #include <qstringlist.h>
00037 #include <qvaluelist.h>
00038 
00039 #include <krfcdate.h>
00040 #include <kdebug.h>
00041 #include <kurl.h>
00042 
00043 
00044 namespace Akregator {
00045 
00046 struct Article::Private : public Shared
00047 {
00058     enum Status {Deleted=0x01, Trash=0x02, New=0x04, Read=0x08, Keep=0x10};
00059 
00060     int status;
00061     QString guid;
00062     uint hash;
00063     Backend::FeedStorage* archive;
00064     QDateTime pubDate;
00065     Feed* feed;
00066 };
00067 
00068 Article::Article() : d(new Private)
00069 {
00070     d->hash = 0;
00071     d->status = 0;
00072     d->feed = 0;
00073     d->archive = 0;
00074     d->pubDate.setTime_t(1);
00075 
00076 }
00077 
00078 Article::Article(const QString& guid, Feed* feed) : d(new Private)
00079 {
00080     d->feed = feed;
00081     d->guid = guid;
00082     d->archive = Backend::Storage::getInstance()->archiveFor(feed->xmlUrl());
00083     d->status = d->archive->status(d->guid);
00084     d->pubDate.setTime_t(d->archive->pubDate(d->guid));
00085     d->hash = d->archive->hash(d->guid);
00086 }
00087 
00088 void Article::initialize(RSS::Article article, Backend::FeedStorage* archive)
00089 {
00090     d->archive = archive;
00091     d->status = Private::New;
00092     d->hash = Utils::calcHash(article.title() + article.description() + article.author() + article.link().url() 
00093                               + article.commentsLink().url() );
00094 
00095     d->guid = article.guid();
00096     
00097     if (!d->archive->contains(d->guid))
00098     {
00099         d->archive->addEntry(d->guid);
00100 
00101         if (article.meta("deleted") == "true") 
00102         { // if article is in deleted state, we just add the status and omit the rest
00103             d->status = Private::Read | Private::Deleted;
00104             d->archive->setStatus(d->guid, d->status);
00105         }
00106         else
00107         { // article is not deleted, let's add it to the archive
00108         
00109             d->archive->setHash(d->guid, d->hash);
00110             QString title = article.title().isEmpty() ? buildTitle(article.description()) :  article.title();
00111             d->archive->setTitle(d->guid, title);
00112             d->archive->setDescription(d->guid, article.description());
00113             d->archive->setLink(d->guid, article.link().url());
00114             d->archive->setComments(d->guid, article.comments());
00115             d->archive->setCommentsLink(d->guid, article.commentsLink().url());
00116             d->archive->setGuidIsPermaLink(d->guid, article.guidIsPermaLink());
00117             d->archive->setGuidIsHash(d->guid, article.meta("guidIsHash") == "true");
00118             d->pubDate = article.pubDate().isValid() ? article.pubDate() : QDateTime::currentDateTime();
00119             d->archive->setPubDate(d->guid, d->pubDate.toTime_t());
00120             d->archive->setAuthor(d->guid, article.author());
00121                         
00122             QValueList<RSS::Category> cats = article.categories();
00123             QValueList<RSS::Category>::ConstIterator end = cats.end();
00124 
00125             for (QValueList<RSS::Category>::ConstIterator it = cats.begin(); it != end; ++it)
00126             {
00127                 Backend::Category cat;
00128 
00129                 cat.term = (*it).category();
00130                 cat.scheme = (*it).domain();
00131                 cat.name = (*it).category();
00132 
00133                 d->archive->addCategory(d->guid, cat);
00134             }
00135 
00136             if (!article.enclosure().isNull())
00137             {
00138                 d->archive->setEnclosure(d->guid, article.enclosure().url(), article.enclosure().type(), article.enclosure().length());
00139             }
00140             else
00141             {
00142                 d->archive->removeEnclosure(d->guid);
00143             }
00144 
00145             QString status = article.meta("status");
00146             
00147             if (!status.isEmpty())
00148             {
00149                 int statusInt = status.toInt();
00150                 if (statusInt == New)
00151                     statusInt = Unread;
00152                 setStatus(statusInt);
00153             }
00154             setKeep(article.meta("keep") == "true");
00155         }
00156     }
00157     else
00158     {
00159         // always update comments count, as it's not used for hash calculation
00160         d->archive->setComments(d->guid, article.comments());
00161         if (d->hash != d->archive->hash(d->guid)) //article is in archive, was it modified?
00162         { // if yes, update
00163             d->pubDate.setTime_t(d->archive->pubDate(d->guid));
00164             d->archive->setHash(d->guid, d->hash);
00165             QString title = article.title().isEmpty() ? buildTitle(article.description()) :  article.title();
00166             d->archive->setTitle(d->guid, title);
00167             d->archive->setDescription(d->guid, article.description());
00168             d->archive->setLink(d->guid, article.link().url());
00169             d->archive->setCommentsLink(d->guid, article.commentsLink().url());
00170             d->archive->setAuthor(d->guid, article.author());
00171         }
00172     }
00173 }
00174 
00175 Article::Article(RSS::Article article, Feed* feed) : d(new Private)
00176 {
00177     //assert(feed)
00178     d->feed = feed;
00179     initialize(article, Backend::Storage::getInstance()->archiveFor(feed->xmlUrl()));
00180 }
00181 
00182 Article::Article(RSS::Article article, Backend::FeedStorage* archive) : d(new Private)
00183 {
00184     d->feed = 0;
00185     initialize(article, archive);
00186 }
00187 
00188 bool Article::isNull() const
00189 {
00190     return d->archive == 0; // TODO: use proper null state
00191 }
00192 
00193 void Article::offsetPubDate(int secs)
00194 {
00195    d->pubDate = d->pubDate.addSecs(secs);
00196    d->archive->setPubDate(d->guid, d->pubDate.toTime_t());
00197 
00198 }
00199 
00200 void Article::setDeleted()
00201 {
00202     if (isDeleted())
00203         return;
00204   
00205     setStatus(Read);
00206     d->status = Private::Deleted | Private::Read;
00207     d->archive->setStatus(d->guid, d->status);
00208     d->archive->setDeleted(d->guid);
00209 
00210     if (d->feed)
00211         d->feed->setArticleDeleted(*this);
00212 }
00213 
00214 bool Article::isDeleted() const
00215 {
00216     return (d->status & Private::Deleted) != 0;
00217 }
00218 
00219 Article::Article(const Article &other) : d(new Private)
00220 {
00221     *this = other;
00222 }
00223 
00224 Article::~Article()
00225 {
00226     if (d->deref())
00227     {
00228         delete d;
00229         d = 0;
00230     }
00231 }
00232 
00233 Article &Article::operator=(const Article &other)
00234 {
00235     if (this != &other) {
00236         other.d->ref();
00237         if (d && d->deref())
00238             delete d;
00239         d = other.d;
00240     }
00241     return *this;
00242 }
00243 
00244 
00245 bool Article::operator<(const Article &other) const
00246 {
00247     return pubDate() > other.pubDate() || 
00248             (pubDate() == other.pubDate() && guid() < other.guid() );
00249 }
00250 
00251 bool Article::operator<=(const Article &other) const
00252 {
00253     return (pubDate() > other.pubDate() || *this == other);
00254 }
00255 
00256 bool Article::operator>(const Article &other) const
00257 {
00258     return pubDate() < other.pubDate() || 
00259             (pubDate() == other.pubDate() && guid() > other.guid() );
00260 }
00261 
00262 bool Article::operator>=(const Article &other) const
00263 {
00264     return (pubDate() > other.pubDate() || *this == other);
00265 }
00266 
00267 bool Article::operator==(const Article &other) const
00268 {
00269     return d->guid == other.guid();
00270 }
00271 
00272 int Article::status() const
00273 {
00274     if ((d->status & Private::Read) != 0)
00275         return Read;
00276 
00277     if ((d->status & Private::New) != 0)
00278         return New;
00279     else
00280         return Unread;
00281 }
00282 
00283 void Article::setStatus(int stat)
00284 {
00285     int oldStatus = status();
00286 
00287     if (oldStatus != stat)
00288     {
00289         switch (stat)
00290         {
00291             case Read:
00292                 d->status = (d->status | Private::Read) & ~Private::New;
00293                 break;
00294             case Unread:
00295                 d->status = (d->status & ~Private::Read) & ~Private::New;
00296                 break;
00297             case New:
00298                 d->status = (d->status | Private::New) & ~Private::Read;
00299                 break;
00300         }
00301         d->archive->setStatus(d->guid, d->status);
00302         if (d->feed)
00303             d->feed->setArticleChanged(*this, oldStatus);
00304      }
00305 }
00306 
00307 QString Article::title() const
00308 {
00309     return d->archive->title(d->guid);
00310 }
00311 
00312 QString Article::author() const
00313 {
00314     return d->archive->author(d->guid);
00315 }
00316 
00317 KURL Article::link() const
00318 {
00319     return d->archive->link(d->guid);
00320 }
00321 
00322 QString Article::description() const
00323 {
00324     return d->archive->description(d->guid);
00325 }
00326 
00327 QString Article::guid() const
00328 {
00329     return d->guid;
00330 }
00331 
00332 KURL Article::commentsLink() const
00333 {
00334     return d->archive->commentsLink(d->guid);
00335 }
00336 
00337 
00338 int Article::comments() const
00339 {
00340     
00341     return d->archive->comments(d->guid);
00342 }
00343 
00344 
00345 bool Article::guidIsPermaLink() const
00346 {
00347     return d->archive->guidIsPermaLink(d->guid);
00348 }
00349 
00350 bool Article::guidIsHash() const
00351 {
00352     return d->archive->guidIsHash(d->guid);
00353 }
00354 
00355 uint Article::hash() const
00356 {
00357     return d->hash;
00358 }
00359 
00360 bool Article::keep() const
00361 {
00362     return (d->status & Private::Keep) != 0;
00363 }
00364 
00365 RSS::Enclosure Article::enclosure() const
00366 {
00367     bool hasEnc;
00368     QString url, type;
00369     int length;
00370     d->archive->enclosure(d->guid, hasEnc, url, type, length);
00371     return hasEnc ? RSS::Enclosure(url, length, type) : RSS::Enclosure();
00372 
00373     
00374 }
00375 
00376 
00377 void Article::setKeep(bool keep)
00378 {
00379     d->status = keep ? (d->status | Private::Keep) : (d->status & ~Private::Keep);
00380     d->archive->setStatus(d->guid, d->status);
00381     if (d->feed)
00382         d->feed->setArticleChanged(*this);
00383 }
00384 
00385 void Article::addTag(const QString& tag)
00386 {
00387     d->archive->addTag(d->guid, tag);
00388     if (d->feed)
00389         d->feed->setArticleChanged(*this);
00390 }
00391 
00392 void Article::removeTag(const QString& tag)
00393 {
00394     d->archive->removeTag(d->guid, tag);
00395     if (d->feed)
00396         d->feed->setArticleChanged(*this);
00397 }
00398 
00399 bool Article::hasTag(const QString& tag) const
00400 {
00401     return d->archive->tags(d->guid).contains(tag);
00402 }
00403 
00404 QStringList Article::tags() const
00405 {
00406     return d->archive->tags(d->guid);
00407 }
00408             
00409 Feed* Article::feed() const
00410 { return d->feed; }
00411 
00412 const QDateTime& Article::pubDate() const
00413 {
00414     return d->pubDate;
00415 }
00416 
00417 QString Article::buildTitle(const QString& description)
00418 {
00419     QString s = description;
00420     if (description.stripWhiteSpace().isEmpty())
00421         return "";
00422         
00423     int i = s.find('>',500); /*avoid processing too much */
00424     if (i != -1)
00425         s = s.left(i+1);
00426     QRegExp rx("(<([^\\s>]*)(?:[^>]*)>)[^<]*", false);
00427     QString tagName, toReplace, replaceWith;
00428     while (rx.search(s) != -1 )
00429     {
00430         tagName=rx.cap(2);
00431         if (tagName=="SCRIPT"||tagName=="script")
00432             toReplace=rx.cap(0); // strip tag AND tag contents
00433         else if (tagName.startsWith("br") || tagName.startsWith("BR"))
00434         {
00435             toReplace=rx.cap(1);
00436             replaceWith=" ";
00437         }
00438         else
00439             toReplace=rx.cap(1);  // strip just tag
00440         s=s.replace(s.find(toReplace),toReplace.length(),replaceWith); // do the deed
00441     }
00442     if (s.length()> 90)
00443         s=s.left(90)+"...";
00444     return s.simplifyWhiteSpace();
00445 }
00446 } // namespace Akregator
KDE Home | KDE Accessibility Home | Description of Access Keys