File indexing completed on 2024-05-12 05:13:02

0001 /*
0002     This file is part of Akregator.
0003 
0004     SPDX-FileCopyrightText: 2005 Frank Osterfeld <osterfeld@kde.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
0007 */
0008 
0009 #include "feedstorage.h"
0010 #include "storage.h"
0011 
0012 #include <Syndication/DocumentSource>
0013 #include <Syndication/Feed>
0014 #include <Syndication/Item>
0015 
0016 #include "mk4.h"
0017 
0018 #include <QDateTime>
0019 #include <QDebug>
0020 #include <QStandardPaths>
0021 
0022 namespace
0023 {
0024 static uint calcHash(const QString &str)
0025 {
0026     if (str.isNull()) { // handle null string as "", prevents crash
0027         return calcHash(QLatin1StringView(""));
0028     }
0029     const char *s = str.toLatin1().constData();
0030     uint hash = 5381;
0031     int c;
0032     while ((c = *s++)) {
0033         hash = ((hash << 5) + hash) + c; // hash*33 + c
0034     }
0035     return hash;
0036 }
0037 }
0038 
0039 namespace Akregator
0040 {
0041 namespace Backend
0042 {
0043 class FeedStorage::FeedStoragePrivate
0044 {
0045 public:
0046     FeedStoragePrivate()
0047         : modified(false)
0048         , pguid("guid")
0049         , ptitle("title")
0050         , pdescription("description")
0051         , pcontent("content")
0052         , plink("link")
0053         , pcommentsLink("commentsLink")
0054         , ptag("tag")
0055         , pEnclosureType("enclosureType")
0056         , pEnclosureUrl("enclosureUrl")
0057         , pcatTerm("catTerm")
0058         , pcatScheme("catScheme")
0059         , pcatName("catName")
0060         , pauthorName("authorName")
0061         , pauthorUri("authorUri")
0062         , pauthorEMail("authorEMail")
0063         , phash("hash")
0064         , pguidIsHash("guidIsHash")
0065         , pguidIsPermaLink("guidIsPermaLink")
0066         , pcomments("comments")
0067         , pstatus("status")
0068         , ppubDate("pubDate")
0069         , pHasEnclosure("hasEnclosure")
0070         , pEnclosureLength("enclosureLength")
0071     {
0072     }
0073 
0074     QString url;
0075     c4_Storage *storage;
0076     Storage *mainStorage;
0077     c4_View archiveView;
0078 
0079     bool autoCommit;
0080     bool modified;
0081     c4_StringProp pguid, ptitle, pdescription, pcontent, plink, pcommentsLink, ptag, pEnclosureType, pEnclosureUrl, pcatTerm, pcatScheme, pcatName, pauthorName,
0082         pauthorUri, pauthorEMail;
0083     c4_IntProp phash, pguidIsHash, pguidIsPermaLink, pcomments, pstatus, ppubDate, pHasEnclosure, pEnclosureLength;
0084 };
0085 
0086 FeedStorage::FeedStorage(const QString &url, Storage *main)
0087     : d(new FeedStoragePrivate)
0088 {
0089     d->autoCommit = main->autoCommit();
0090     d->url = url;
0091     d->mainStorage = main;
0092 
0093     QString url2 = url;
0094 
0095     if (url.length() > 255) {
0096         url2 = url.left(200) + QString::number(::calcHash(url), 16);
0097     }
0098 
0099     qDebug() << url2;
0100     QString t = url2;
0101     QString t2 = url2;
0102     QString filePath = main->archivePath() + QLatin1Char('/') + t.replace(QLatin1Char('/'), QLatin1Char('_')).replace(QLatin1Char(':'), QLatin1Char('_'));
0103     d->storage = new c4_Storage(QString(filePath + QLatin1StringView(".mk4")).toLocal8Bit().constData(), true);
0104 
0105     d->archiveView = d->storage->GetAs(
0106         "articles[guid:S,title:S,hash:I,guidIsHash:I,guidIsPermaLink:I,description:S,link:S,comments:I,commentsLink:S,status:I,pubDate:I,tags[tag:S],"
0107         "hasEnclosure:I,enclosureUrl:S,enclosureType:S,enclosureLength:I,categories[catTerm:S,catScheme:S,catName:S],authorName:S,content:S,authorUri:S,"
0108         "authorEMail:S]");
0109 
0110     c4_View hash = d->storage->GetAs("archiveHash[_H:I,_R:I]");
0111     d->archiveView = d->archiveView.Hash(hash, 1); // hash on guid
0112 }
0113 
0114 FeedStorage::~FeedStorage()
0115 {
0116     delete d->storage;
0117 }
0118 
0119 void FeedStorage::markDirty()
0120 {
0121     if (!d->modified) {
0122         d->modified = true;
0123         // Tell this to mainStorage
0124         d->mainStorage->markDirty();
0125     }
0126 }
0127 
0128 void FeedStorage::commit()
0129 {
0130     if (d->modified) {
0131         d->storage->Commit();
0132     }
0133     d->modified = false;
0134 }
0135 
0136 void FeedStorage::rollback()
0137 {
0138     d->storage->Rollback();
0139 }
0140 
0141 void FeedStorage::close()
0142 {
0143     if (d->autoCommit) {
0144         commit();
0145     }
0146 }
0147 
0148 int FeedStorage::unread() const
0149 {
0150     return d->mainStorage->unreadFor(d->url);
0151 }
0152 
0153 void FeedStorage::setUnread(int unread)
0154 {
0155     d->mainStorage->setUnreadFor(d->url, unread);
0156 }
0157 
0158 int FeedStorage::totalCount() const
0159 {
0160     return d->mainStorage->totalCountFor(d->url);
0161 }
0162 
0163 void FeedStorage::setTotalCount(int total)
0164 {
0165     d->mainStorage->setTotalCountFor(d->url, total);
0166 }
0167 
0168 QDateTime FeedStorage::lastFetch() const
0169 {
0170     return d->mainStorage->lastFetchFor(d->url);
0171 }
0172 
0173 void FeedStorage::setLastFetch(const QDateTime &lastFetch)
0174 {
0175     d->mainStorage->setLastFetchFor(d->url, lastFetch);
0176 }
0177 
0178 QStringList FeedStorage::articles() const
0179 {
0180     QStringList list;
0181     int size = d->archiveView.GetSize();
0182     list.reserve(size);
0183     for (int i = 0; i < size; ++i) { // fill with guids
0184         list += QString::fromLatin1(QByteArray(d->pguid(d->archiveView.GetAt(i))));
0185     }
0186     return list;
0187 }
0188 
0189 void FeedStorage::addEntry(const QString &guid)
0190 {
0191     c4_Row row;
0192     d->pguid(row) = guid.toLatin1().constData();
0193     if (!contains(guid)) {
0194         d->archiveView.Add(row);
0195         markDirty();
0196         setTotalCount(totalCount() + 1);
0197     }
0198 }
0199 
0200 bool FeedStorage::contains(const QString &guid) const
0201 {
0202     return findArticle(guid) != -1;
0203 }
0204 
0205 int FeedStorage::findArticle(const QString &guid) const
0206 {
0207     c4_Row findrow;
0208     d->pguid(findrow) = guid.toLatin1().constData();
0209     return d->archiveView.Find(findrow);
0210 }
0211 
0212 void FeedStorage::deleteArticle(const QString &guid)
0213 {
0214     int findidx = findArticle(guid);
0215     if (findidx != -1) {
0216         setTotalCount(totalCount() - 1);
0217         d->archiveView.RemoveAt(findidx);
0218         markDirty();
0219     }
0220 }
0221 
0222 bool FeedStorage::guidIsHash(const QString &guid) const
0223 {
0224     int findidx = findArticle(guid);
0225     return findidx != -1 ? d->pguidIsHash(d->archiveView.GetAt(findidx)) : false;
0226 }
0227 
0228 bool FeedStorage::guidIsPermaLink(const QString &guid) const
0229 {
0230     int findidx = findArticle(guid);
0231     return findidx != -1 ? d->pguidIsPermaLink(d->archiveView.GetAt(findidx)) : false;
0232 }
0233 
0234 uint FeedStorage::hash(const QString &guid) const
0235 {
0236     int findidx = findArticle(guid);
0237     return findidx != -1 ? d->phash(d->archiveView.GetAt(findidx)) : 0;
0238 }
0239 
0240 void FeedStorage::setDeleted(const QString &guid)
0241 {
0242     int findidx = findArticle(guid);
0243     if (findidx == -1) {
0244         return;
0245     }
0246 
0247     c4_Row row;
0248     row = d->archiveView.GetAt(findidx);
0249     d->pdescription(row) = "";
0250     d->pcontent(row) = "";
0251     d->ptitle(row) = "";
0252     d->plink(row) = "";
0253     d->pauthorName(row) = "";
0254     d->pauthorUri(row) = "";
0255     d->pauthorEMail(row) = "";
0256     d->pcommentsLink(row) = "";
0257     d->archiveView.SetAt(findidx, row);
0258     markDirty();
0259 }
0260 
0261 QString FeedStorage::link(const QString &guid) const
0262 {
0263     int findidx = findArticle(guid);
0264     return findidx != -1 ? QString::fromUtf8(QByteArray(d->plink(d->archiveView.GetAt(findidx)))) : QLatin1StringView("");
0265 }
0266 
0267 QDateTime FeedStorage::pubDate(const QString &guid) const
0268 {
0269     int findidx = findArticle(guid);
0270     return findidx != -1 ? QDateTime::fromSecsSinceEpoch(d->ppubDate(d->archiveView.GetAt(findidx))) : QDateTime();
0271 }
0272 
0273 int FeedStorage::status(const QString &guid) const
0274 {
0275     int findidx = findArticle(guid);
0276     return findidx != -1 ? d->pstatus(d->archiveView.GetAt(findidx)) : 0;
0277 }
0278 
0279 void FeedStorage::setStatus(const QString &guid, int status)
0280 {
0281     int findidx = findArticle(guid);
0282     if (findidx == -1) {
0283         return;
0284     }
0285     c4_Row row;
0286     row = d->archiveView.GetAt(findidx);
0287     d->pstatus(row) = status;
0288     d->archiveView.SetAt(findidx, row);
0289     markDirty();
0290 }
0291 
0292 void FeedStorage::article(const QString &guid, uint &hash, QString &title, int &status, QDateTime &pubDate) const
0293 {
0294     int idx = findArticle(guid);
0295     if (idx != -1) {
0296         auto view = d->archiveView.GetAt(idx);
0297         hash = d->phash(view);
0298         title = QString::fromUtf8(QByteArray(d->ptitle(view)));
0299         status = d->pstatus(view);
0300         pubDate = QDateTime::fromSecsSinceEpoch(d->ppubDate(view));
0301     }
0302 }
0303 
0304 QString FeedStorage::title(const QString &guid) const
0305 {
0306     int findidx = findArticle(guid);
0307     return findidx != -1 ? QString::fromUtf8(QByteArray(d->ptitle(d->archiveView.GetAt(findidx)))) : QLatin1StringView("");
0308 }
0309 
0310 QString FeedStorage::description(const QString &guid) const
0311 {
0312     int findidx = findArticle(guid);
0313     return findidx != -1 ? QString::fromUtf8(QByteArray(d->pdescription(d->archiveView.GetAt(findidx)))) : QLatin1StringView("");
0314 }
0315 
0316 QString FeedStorage::content(const QString &guid) const
0317 {
0318     int findidx = findArticle(guid);
0319     return findidx != -1 ? QString::fromUtf8(QByteArray(d->pcontent(d->archiveView.GetAt(findidx)))) : QLatin1StringView("");
0320 }
0321 
0322 void FeedStorage::setPubDate(const QString &guid, const QDateTime &pubdate)
0323 {
0324     int findidx = findArticle(guid);
0325     if (findidx == -1) {
0326         return;
0327     }
0328     c4_Row row;
0329     row = d->archiveView.GetAt(findidx);
0330     d->ppubDate(row) = pubdate.toSecsSinceEpoch();
0331     d->archiveView.SetAt(findidx, row);
0332     markDirty();
0333 }
0334 
0335 void FeedStorage::setGuidIsHash(const QString &guid, bool isHash)
0336 {
0337     int findidx = findArticle(guid);
0338     if (findidx == -1) {
0339         return;
0340     }
0341     c4_Row row;
0342     row = d->archiveView.GetAt(findidx);
0343     d->pguidIsHash(row) = isHash;
0344     d->archiveView.SetAt(findidx, row);
0345     markDirty();
0346 }
0347 
0348 void FeedStorage::setLink(const QString &guid, const QString &link)
0349 {
0350     int findidx = findArticle(guid);
0351     if (findidx == -1) {
0352         return;
0353     }
0354     c4_Row row;
0355     row = d->archiveView.GetAt(findidx);
0356     d->plink(row) = !link.isEmpty() ? link.toUtf8().constData() : "";
0357     d->archiveView.SetAt(findidx, row);
0358     markDirty();
0359 }
0360 
0361 void FeedStorage::setHash(const QString &guid, uint hash)
0362 {
0363     int findidx = findArticle(guid);
0364     if (findidx == -1) {
0365         return;
0366     }
0367     c4_Row row;
0368     row = d->archiveView.GetAt(findidx);
0369     d->phash(row) = hash;
0370     d->archiveView.SetAt(findidx, row);
0371     markDirty();
0372 }
0373 
0374 void FeedStorage::setTitle(const QString &guid, const QString &title)
0375 {
0376     int findidx = findArticle(guid);
0377     if (findidx == -1) {
0378         return;
0379     }
0380     c4_Row row;
0381     row = d->archiveView.GetAt(findidx);
0382     d->ptitle(row) = !title.isEmpty() ? title.toUtf8().data() : "";
0383     d->archiveView.SetAt(findidx, row);
0384     markDirty();
0385 }
0386 
0387 void FeedStorage::setDescription(const QString &guid, const QString &description)
0388 {
0389     int findidx = findArticle(guid);
0390     if (findidx == -1) {
0391         return;
0392     }
0393     c4_Row row;
0394     row = d->archiveView.GetAt(findidx);
0395     d->pdescription(row) = !description.isEmpty() ? description.toUtf8().data() : "";
0396     d->archiveView.SetAt(findidx, row);
0397     markDirty();
0398 }
0399 
0400 void FeedStorage::setContent(const QString &guid, const QString &content)
0401 {
0402     int findidx = findArticle(guid);
0403     if (findidx == -1) {
0404         return;
0405     }
0406     c4_Row row;
0407     row = d->archiveView.GetAt(findidx);
0408     d->pcontent(row) = !content.isEmpty() ? content.toUtf8().data() : "";
0409     d->archiveView.SetAt(findidx, row);
0410     markDirty();
0411 }
0412 
0413 void FeedStorage::setAuthorName(const QString &guid, const QString &author)
0414 {
0415     int findidx = findArticle(guid);
0416     if (findidx == -1) {
0417         return;
0418     }
0419     c4_Row row;
0420     row = d->archiveView.GetAt(findidx);
0421     d->pauthorName(row) = !author.isEmpty() ? author.toUtf8().data() : "";
0422     d->archiveView.SetAt(findidx, row);
0423     markDirty();
0424 }
0425 
0426 void FeedStorage::setAuthorUri(const QString &guid, const QString &author)
0427 {
0428     int findidx = findArticle(guid);
0429     if (findidx == -1) {
0430         return;
0431     }
0432     c4_Row row;
0433     row = d->archiveView.GetAt(findidx);
0434     d->pauthorUri(row) = !author.isEmpty() ? author.toUtf8().data() : "";
0435     d->archiveView.SetAt(findidx, row);
0436     markDirty();
0437 }
0438 
0439 void FeedStorage::setAuthorEMail(const QString &guid, const QString &author)
0440 {
0441     int findidx = findArticle(guid);
0442     if (findidx == -1) {
0443         return;
0444     }
0445     c4_Row row;
0446     row = d->archiveView.GetAt(findidx);
0447     d->pauthorEMail(row) = !author.isEmpty() ? author.toUtf8().data() : "";
0448     d->archiveView.SetAt(findidx, row);
0449     markDirty();
0450 }
0451 
0452 QString FeedStorage::authorName(const QString &guid) const
0453 {
0454     int findidx = findArticle(guid);
0455     return findidx != -1 ? QString::fromUtf8(QByteArray(d->pauthorName(d->archiveView.GetAt(findidx)))) : QString();
0456 }
0457 
0458 QString FeedStorage::authorUri(const QString &guid) const
0459 {
0460     int findidx = findArticle(guid);
0461     return findidx != -1 ? QString::fromUtf8(QByteArray(d->pauthorUri(d->archiveView.GetAt(findidx)))) : QString();
0462 }
0463 
0464 QString FeedStorage::authorEMail(const QString &guid) const
0465 {
0466     int findidx = findArticle(guid);
0467     return findidx != -1 ? QString::fromUtf8(QByteArray(d->pauthorEMail(d->archiveView.GetAt(findidx)))) : QString();
0468 }
0469 
0470 void FeedStorage::setGuidIsPermaLink(const QString &guid, bool isPermaLink)
0471 {
0472     int findidx = findArticle(guid);
0473     if (findidx == -1) {
0474         return;
0475     }
0476     c4_Row row;
0477     row = d->archiveView.GetAt(findidx);
0478     d->pguidIsPermaLink(row) = isPermaLink;
0479     d->archiveView.SetAt(findidx, row);
0480     markDirty();
0481 }
0482 
0483 void FeedStorage::setEnclosure(const QString &guid, const QString &url, const QString &type, int length)
0484 {
0485     int findidx = findArticle(guid);
0486     if (findidx == -1) {
0487         return;
0488     }
0489     c4_Row row;
0490     row = d->archiveView.GetAt(findidx);
0491     d->pHasEnclosure(row) = true;
0492     d->pEnclosureUrl(row) = !url.isEmpty() ? url.toUtf8().data() : "";
0493     d->pEnclosureType(row) = !type.isEmpty() ? type.toUtf8().data() : "";
0494     d->pEnclosureLength(row) = length;
0495 
0496     d->archiveView.SetAt(findidx, row);
0497     markDirty();
0498 }
0499 
0500 void FeedStorage::removeEnclosure(const QString &guid)
0501 {
0502     int findidx = findArticle(guid);
0503     if (findidx == -1) {
0504         return;
0505     }
0506     c4_Row row;
0507     row = d->archiveView.GetAt(findidx);
0508     d->pHasEnclosure(row) = false;
0509     d->pEnclosureUrl(row) = "";
0510     d->pEnclosureType(row) = "";
0511     d->pEnclosureLength(row) = -1;
0512 
0513     d->archiveView.SetAt(findidx, row);
0514     markDirty();
0515 }
0516 
0517 void FeedStorage::enclosure(const QString &guid, bool &hasEnclosure, QString &url, QString &type, int &length) const
0518 {
0519     int findidx = findArticle(guid);
0520     if (findidx == -1) {
0521         hasEnclosure = false;
0522         url.clear();
0523         type.clear();
0524         length = -1;
0525         return;
0526     }
0527     c4_Row row = d->archiveView.GetAt(findidx);
0528     hasEnclosure = d->pHasEnclosure(row);
0529     url = QLatin1StringView(d->pEnclosureUrl(row));
0530     type = QLatin1StringView(d->pEnclosureType(row));
0531     length = d->pEnclosureLength(row);
0532 }
0533 
0534 void FeedStorage::setCategories(const QString &, const QStringList &categories)
0535 {
0536     // TODO
0537 }
0538 
0539 QStringList FeedStorage::categories(const QString &guid) const
0540 {
0541     // TODO
0542     return {};
0543 }
0544 } // namespace Backend
0545 } // namespace Akregator