00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025 #include "article.h"
00026 #include "feed.h"
00027 #include "feedstorage.h"
00028 #include "shared.h"
00029 #include "storage.h"
00030 #include "utils.h"
00031
00032 #include <syndication/syndication.h>
00033
00034 #include <QDateTime>
00035 #include <qdom.h>
00036 #include <QRegExp>
00037 #include <QList>
00038
00039 #include <kdebug.h>
00040 #include <kurl.h>
00041
00042 #include <cassert>
00043
00044 using namespace Syndication;
00045
00046 namespace {
00047
00048 QString buildTitle(const QString& description)
00049 {
00050 QString s = description;
00051 if (description.trimmed().isEmpty())
00052 return "";
00053
00054 int i = s.indexOf('>',500);
00055 if (i != -1)
00056 s = s.left(i+1);
00057 QRegExp rx("(<([^\\s>]*)(?:[^>]*)>)[^<]*", Qt::CaseInsensitive);
00058 QString tagName, toReplace, replaceWith;
00059 while (rx.indexIn(s) != -1 )
00060 {
00061 tagName=rx.cap(2);
00062 if (tagName=="SCRIPT"||tagName=="script")
00063 toReplace=rx.cap(0);
00064 else if (tagName.startsWith("br") || tagName.startsWith("BR"))
00065 {
00066 toReplace=rx.cap(1);
00067 replaceWith=" ";
00068 }
00069 else
00070 toReplace=rx.cap(1);
00071 s=s.replace(s.indexOf(toReplace),toReplace.length(),replaceWith);
00072 }
00073 if (s.length()> 90)
00074 s=s.left(90)+"...";
00075 return s.simplified();
00076 }
00077
00078 }
00079
00080 namespace Akregator {
00081
00082 struct Article::Private : public Shared
00083 {
00084 Private();
00085 Private( const QString& guid, Feed* feed, Backend::FeedStorage* archive );
00086 Private( const ItemPtr& article, Feed* feed, Backend::FeedStorage* archive );
00087
00097 enum Status
00098 {
00099 Deleted=0x01,
00100 Trash=0x02,
00101 New=0x04,
00102 Read=0x08,
00103 Keep=0x10
00104 };
00105
00106 Feed* feed;
00107 QString guid;
00108 Backend::FeedStorage* archive;
00109 int status;
00110 uint hash;
00111 QDateTime pubDate;
00112 };
00113
00114 Article::Private::Private()
00115 : feed( 0 ),
00116 archive( 0 ),
00117 status( 0 ),
00118 hash( 0 ),
00119 pubDate( QDateTime::fromTime_t( 1 ) )
00120 {
00121 }
00122
00123 Article::Private::Private( const QString& guid_, Feed* feed_, Backend::FeedStorage* archive_ )
00124 : feed( feed_ ),
00125 guid( guid_ ),
00126 archive( archive_ ),
00127 status( archive->status( guid ) ),
00128 hash( archive->hash( guid ) ),
00129 pubDate( QDateTime::fromTime_t( archive->pubDate( guid ) ) )
00130 {
00131 }
00132
00133 Article::Private::Private( const ItemPtr& article, Feed* feed_, Backend::FeedStorage* archive_ )
00134 : feed( feed_ ),
00135 archive( archive_ ),
00136 status ( New ),
00137 hash( 0 )
00138 {
00139 assert( archive );
00140 const QList<PersonPtr> authorList = article->authors();
00141
00142 QString author;
00143
00144 const PersonPtr firstAuthor = !authorList.isEmpty() ? authorList.first() : PersonPtr();
00145
00146 hash = Utils::calcHash(article->title() + article->description() + article->content() + article->link() + author);
00147
00148 guid = article->id();
00149
00150 if (!archive->contains(guid))
00151 {
00152 archive->addEntry(guid);
00153
00154 archive->setHash(guid, hash);
00155 QString title = article->title();
00156 if (title.isEmpty())
00157 title = buildTitle(article->description());
00158 archive->setTitle(guid, title);
00159 archive->setContent(guid, article->content());
00160 archive->setDescription(guid, article->description());
00161 archive->setLink(guid, article->link());
00162
00163
00164 archive->setGuidIsPermaLink(guid, false);
00165 archive->setGuidIsHash(guid, guid.startsWith("hash:"));
00166 const time_t datePublished = article->datePublished();
00167 if ( datePublished > 0 )
00168 pubDate.setTime_t( datePublished );
00169 else
00170 pubDate = QDateTime::currentDateTime();
00171 archive->setPubDate(guid, pubDate.toTime_t());
00172 if ( firstAuthor ) {
00173 archive->setAuthorName(guid, firstAuthor->name() );
00174 archive->setAuthorUri(guid, firstAuthor->uri() );
00175 archive->setAuthorEMail(guid, firstAuthor->email() );
00176 }
00177 }
00178 else
00179 {
00180
00181
00182 if (hash != archive->hash(guid))
00183 {
00184 pubDate.setTime_t(archive->pubDate(guid));
00185 archive->setHash(guid, hash);
00186 QString title = article->title();
00187 if (title.isEmpty())
00188 title = buildTitle(article->description());
00189 archive->setTitle(guid, title);
00190 archive->setDescription(guid, article->description());
00191 archive->setContent(guid, article->content());
00192 archive->setLink(guid, article->link());
00193 if ( firstAuthor ) {
00194 archive->setAuthorName(guid, firstAuthor->name() );
00195 archive->setAuthorUri(guid, firstAuthor->uri() );
00196 archive->setAuthorEMail(guid, firstAuthor->email() );
00197 }
00198
00199 }
00200 }
00201
00202 }
00203
00204
00205 Article::Article() : d( new Private )
00206 {
00207 }
00208
00209 Article::Article( const QString& guid, Feed* feed ) : d( new Private( guid, feed, feed->storage()->archiveFor( feed->xmlUrl() ) ) )
00210 {
00211 }
00212
00213 Article::Article( const ItemPtr& article, Feed* feed ) : d( new Private( article, feed, feed->storage()->archiveFor( feed->xmlUrl() ) ) )
00214 {
00215 }
00216
00217 Article::Article( const ItemPtr& article, Backend::FeedStorage* archive ) : d( new Private( article, 0, archive ) )
00218 {
00219 }
00220
00221 bool Article::isNull() const
00222 {
00223 return d->archive == 0;
00224 }
00225
00226 void Article::offsetPubDate(int secs)
00227 {
00228 d->pubDate = d->pubDate.addSecs(secs);
00229 d->archive->setPubDate(d->guid, d->pubDate.toTime_t());
00230
00231 }
00232
00233 void Article::setDeleted()
00234 {
00235 if (isDeleted())
00236 return;
00237
00238 setStatus(Read);
00239 d->status = Private::Deleted | Private::Read;
00240 d->archive->setStatus(d->guid, d->status);
00241 d->archive->setDeleted(d->guid);
00242
00243 if (d->feed)
00244 d->feed->setArticleDeleted(*this);
00245 }
00246
00247 bool Article::isDeleted() const
00248 {
00249 return (d->status & Private::Deleted) != 0;
00250 }
00251
00252 Article::Article(const Article &other) : d( other.d )
00253 {
00254 d->ref();
00255 }
00256
00257 Article::~Article()
00258 {
00259 if ( d->deref() )
00260 {
00261 delete d;
00262 d = 0;
00263 }
00264 }
00265
00266 Article &Article::operator=(const Article &other)
00267 {
00268 Article copy( other );
00269 swap( copy );
00270 return *this;
00271 }
00272
00273
00274 bool Article::operator<(const Article &other) const
00275 {
00276 return pubDate() > other.pubDate() ||
00277 (pubDate() == other.pubDate() && guid() < other.guid() );
00278 }
00279
00280 bool Article::operator<=(const Article &other) const
00281 {
00282 return (pubDate() > other.pubDate() || *this == other);
00283 }
00284
00285 bool Article::operator>(const Article &other) const
00286 {
00287 return pubDate() < other.pubDate() ||
00288 (pubDate() == other.pubDate() && guid() > other.guid() );
00289 }
00290
00291 bool Article::operator>=(const Article &other) const
00292 {
00293 return (pubDate() > other.pubDate() || *this == other);
00294 }
00295
00296 bool Article::operator==(const Article &other) const
00297 {
00298 return d->guid == other.guid();
00299 }
00300
00301 bool Article::operator!=(const Article &other) const
00302 {
00303 return d->guid != other.guid();
00304 }
00305
00306 int Article::status() const
00307 {
00308 if ((d->status & Private::Read) != 0)
00309 return Read;
00310
00311 if ((d->status & Private::New) != 0)
00312 return New;
00313
00314 return Unread;
00315 }
00316
00317 void Article::setStatus(int stat)
00318 {
00319 int oldStatus = status();
00320
00321 if (oldStatus != stat)
00322 {
00323 switch (stat)
00324 {
00325 case Read:
00326 d->status = (d->status | Private::Read) & ~Private::New;
00327 break;
00328 case Unread:
00329 d->status = (d->status & ~Private::Read) & ~Private::New;
00330 break;
00331 case New:
00332 d->status = (d->status | Private::New) & ~Private::Read;
00333 break;
00334 }
00335 d->archive->setStatus(d->guid, d->status);
00336 if (d->feed)
00337 d->feed->setArticleChanged(*this, oldStatus);
00338 }
00339 }
00340
00341 QString Article::title() const
00342 {
00343 return d->archive->title(d->guid);
00344 }
00345
00346 QString Article::authorName() const
00347 {
00348 return d->archive->authorName(d->guid);
00349 }
00350
00351 QString Article::authorEMail() const
00352 {
00353 return d->archive->authorEMail(d->guid);
00354 }
00355
00356 QString Article::authorUri() const
00357 {
00358 return d->archive->authorUri(d->guid);
00359 }
00360 QString Article::authorShort() const {
00361 const QString name = authorName();
00362 if ( !name.isEmpty() )
00363 return name;
00364 const QString email = authorEMail();
00365 if ( !email.isEmpty() )
00366 return email;
00367 const QString uri = authorUri();
00368 if ( !uri.isEmpty() )
00369 return uri;
00370 return QString();
00371 }
00372
00373 QString Article::authorAsHtml() const {
00374 const QString name = authorName();
00375 const QString email = authorEMail();
00376
00377 if (!email.isEmpty())
00378 if (!name.isEmpty())
00379 return QString("<a href=\"mailto:%1\">%2</a>").arg( email, name );
00380 else
00381 return QString("<a href=\"mailto:%1\">%1</a>").arg( email );
00382
00383 const QString uri = authorUri();
00384 if (!name.isEmpty())
00385 if (!uri.isEmpty())
00386 return QString("<a href=\"%1\">%2</a>").arg( uri, name );
00387 else
00388 return name;
00389 if ( !uri.isEmpty() )
00390 return QString( "<a href=\"%1\">%1</a>" ).arg( uri );
00391 return QString();
00392 }
00393
00394 KUrl Article::link() const
00395 {
00396 return d->archive->link(d->guid);
00397 }
00398
00399 QString Article::description() const
00400 {
00401 return d->archive->description(d->guid);
00402 }
00403
00404 QString Article::content( ContentOption opt ) const
00405 {
00406 const QString cnt = d->archive->content( d->guid );
00407 return opt == ContentAndOnlyContent ? cnt : ( !cnt.isEmpty() ? cnt : description() );
00408 }
00409
00410 QString Article::guid() const
00411 {
00412 return d->guid;
00413 }
00414
00415 KUrl Article::commentsLink() const
00416 {
00417 return d->archive->commentsLink(d->guid);
00418 }
00419
00420
00421 int Article::comments() const
00422 {
00423 return d->archive->comments(d->guid);
00424 }
00425
00426
00427 bool Article::guidIsPermaLink() const
00428 {
00429 return d->archive->guidIsPermaLink(d->guid);
00430 }
00431
00432 bool Article::guidIsHash() const
00433 {
00434 return d->archive->guidIsHash(d->guid);
00435 }
00436
00437 uint Article::hash() const
00438 {
00439 return d->hash;
00440 }
00441
00442 bool Article::keep() const
00443 {
00444 return (d->status & Private::Keep) != 0;
00445 }
00446
00447 void Article::setKeep(bool keep)
00448 {
00449 d->status = keep ? (d->status | Private::Keep) : (d->status & ~Private::Keep);
00450 d->archive->setStatus(d->guid, d->status);
00451 if (d->feed)
00452 d->feed->setArticleChanged(*this);
00453 }
00454
00455 Feed* Article::feed() const
00456 { return d->feed; }
00457
00458 const QDateTime& Article::pubDate() const
00459 {
00460 return d->pubDate;
00461 }
00462
00463 }