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 "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 {
00103 d->status = Private::Read | Private::Deleted;
00104 d->archive->setStatus(d->guid, d->status);
00105 }
00106 else
00107 {
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
00160 d->archive->setComments(d->guid, article.comments());
00161 if (d->hash != d->archive->hash(d->guid))
00162 {
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
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;
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);
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);
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);
00440 s=s.replace(s.find(toReplace),toReplace.length(),replaceWith);
00441 }
00442 if (s.length()> 90)
00443 s=s.left(90)+"...";
00444 return s.simplifyWhiteSpace();
00445 }
00446 }