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

0001 /*
0002     This file is part of Akregator.
0003 
0004     SPDX-FileCopyrightText: 2005 Stanislav Karchebny <Stanislav.Karchebny@kdemail.net>
0005     SPDX-FileCopyrightText: 2005 Frank Osterfeld <osterfeld@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
0008 */
0009 #include "storage.h"
0010 
0011 #include "mk4.h"
0012 
0013 #include <QMap>
0014 #include <QString>
0015 #include <QStringList>
0016 #include <QTimer>
0017 
0018 #include <QDateTime>
0019 #include <QDir>
0020 #include <QStandardPaths>
0021 #include <chrono>
0022 
0023 using namespace std::chrono_literals;
0024 
0025 class Akregator::Backend::Storage::StoragePrivate
0026 {
0027 public:
0028     StoragePrivate()
0029         : purl("url")
0030         , pFeedList("feedList")
0031         , punread("unread")
0032         , ptotalCount("totalCount")
0033         , plastFetch("lastFetch")
0034     {
0035     }
0036 
0037     c4_Storage *storage;
0038     Akregator::Backend::Storage *q;
0039     c4_View archiveView;
0040     bool autoCommit = false;
0041     bool modified = false;
0042     mutable QMap<QString, Akregator::Backend::FeedStorage *> feeds;
0043     QStringList feedURLs;
0044     c4_StringProp purl, pFeedList;
0045     c4_IntProp punread, ptotalCount, plastFetch;
0046     QString archivePath;
0047 
0048     c4_Storage *feedListStorage;
0049     c4_View feedListView;
0050 
0051     Akregator::Backend::FeedStorage *createFeedStorage(const QString &url);
0052 };
0053 
0054 Akregator::Backend::Storage::Storage()
0055     : d(new StoragePrivate)
0056 {
0057     d->q = this;
0058     setArchivePath(QString());
0059 }
0060 
0061 Akregator::Backend::FeedStorage *Akregator::Backend::Storage::StoragePrivate::createFeedStorage(const QString &url)
0062 {
0063     if (!feeds.contains(url)) {
0064         auto fs = new Akregator::Backend::FeedStorage(url, q);
0065         feeds[url] = fs;
0066         c4_Row findrow;
0067         purl(findrow) = url.toLatin1().constData();
0068         int findidx = archiveView.Find(findrow);
0069         if (findidx == -1) {
0070             punread(findrow) = 0;
0071             ptotalCount(findrow) = 0;
0072             plastFetch(findrow) = 0;
0073             archiveView.Add(findrow);
0074             modified = true;
0075         }
0076     }
0077     return feeds[url];
0078 }
0079 
0080 Akregator::Backend::FeedStorage *Akregator::Backend::Storage::archiveFor(const QString &url)
0081 {
0082     return d->createFeedStorage(url);
0083 }
0084 
0085 const Akregator::Backend::FeedStorage *Akregator::Backend::Storage::archiveFor(const QString &url) const
0086 {
0087     return d->createFeedStorage(url);
0088 }
0089 
0090 void Akregator::Backend::Storage::setArchivePath(const QString &archivePath)
0091 {
0092     if (archivePath.isNull()) { // if isNull, reset to default
0093         d->archivePath = defaultArchivePath();
0094     } else {
0095         d->archivePath = archivePath;
0096     }
0097 }
0098 
0099 QString Akregator::Backend::Storage::archivePath() const
0100 {
0101     return d->archivePath;
0102 }
0103 
0104 QString Akregator::Backend::Storage::defaultArchivePath()
0105 {
0106     const QString ret = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/akregator/Archive");
0107     QDir().mkpath(ret);
0108     return ret;
0109 }
0110 
0111 Akregator::Backend::Storage::~Storage()
0112 {
0113     close();
0114 }
0115 
0116 bool Akregator::Backend::Storage::open(bool autoCommit)
0117 {
0118     QString filePath = d->archivePath + QLatin1StringView("/archiveindex.mk4");
0119     d->storage = new c4_Storage(filePath.toLocal8Bit().constData(), true);
0120     d->archiveView = d->storage->GetAs("archive[url:S,unread:I,totalCount:I,lastFetch:I]");
0121     c4_View hash = d->storage->GetAs("archiveHash[_H:I,_R:I]");
0122     d->archiveView = d->archiveView.Hash(hash, 1); // hash on url
0123     d->autoCommit = autoCommit;
0124 
0125     filePath = d->archivePath + QLatin1StringView("/feedlistbackup.mk4");
0126     d->feedListStorage = new c4_Storage(filePath.toLocal8Bit().constData(), true);
0127     d->feedListView = d->feedListStorage->GetAs("archive[feedList:S,tagSet:S]");
0128     return true;
0129 }
0130 
0131 bool Akregator::Backend::Storage::autoCommit() const
0132 {
0133     return d->autoCommit;
0134 }
0135 
0136 void Akregator::Backend::Storage::close()
0137 {
0138     QMap<QString, FeedStorage *>::Iterator it;
0139     QMap<QString, FeedStorage *>::Iterator end(d->feeds.end());
0140     for (it = d->feeds.begin(); it != end; ++it) {
0141         it.value()->close();
0142         delete it.value();
0143     }
0144     if (d->autoCommit) {
0145         d->storage->Commit();
0146     }
0147 
0148     delete d->storage;
0149     d->storage = nullptr;
0150 
0151     d->feedListStorage->Commit();
0152     delete d->feedListStorage;
0153     d->feedListStorage = nullptr;
0154 }
0155 
0156 bool Akregator::Backend::Storage::commit()
0157 {
0158     QMap<QString, FeedStorage *>::Iterator it;
0159     QMap<QString, FeedStorage *>::Iterator end(d->feeds.end());
0160     for (it = d->feeds.begin(); it != end; ++it) {
0161         it.value()->commit();
0162     }
0163 
0164     if (d->storage) {
0165         d->storage->Commit();
0166         return true;
0167     }
0168 
0169     return false;
0170 }
0171 
0172 bool Akregator::Backend::Storage::rollback()
0173 {
0174     QMap<QString, FeedStorage *>::Iterator it;
0175     QMap<QString, FeedStorage *>::Iterator end(d->feeds.end());
0176     for (it = d->feeds.begin(); it != end; ++it) {
0177         it.value()->rollback();
0178     }
0179 
0180     if (d->storage) {
0181         d->storage->Rollback();
0182         return true;
0183     }
0184     return false;
0185 }
0186 
0187 int Akregator::Backend::Storage::unreadFor(const QString &url) const
0188 {
0189     c4_Row findrow;
0190     d->purl(findrow) = url.toLatin1().constData();
0191     int findidx = d->archiveView.Find(findrow);
0192 
0193     return findidx != -1 ? d->punread(d->archiveView.GetAt(findidx)) : 0;
0194 }
0195 
0196 void Akregator::Backend::Storage::setUnreadFor(const QString &url, int unread)
0197 {
0198     c4_Row findrow;
0199     d->purl(findrow) = url.toLatin1().constData();
0200     int findidx = d->archiveView.Find(findrow);
0201     if (findidx == -1) {
0202         return;
0203     }
0204     findrow = d->archiveView.GetAt(findidx);
0205     d->punread(findrow) = unread;
0206     d->archiveView.SetAt(findidx, findrow);
0207     markDirty();
0208 }
0209 
0210 int Akregator::Backend::Storage::totalCountFor(const QString &url) const
0211 {
0212     c4_Row findrow;
0213     d->purl(findrow) = url.toLatin1().constData();
0214     int findidx = d->archiveView.Find(findrow);
0215 
0216     return findidx != -1 ? d->ptotalCount(d->archiveView.GetAt(findidx)) : 0;
0217 }
0218 
0219 void Akregator::Backend::Storage::setTotalCountFor(const QString &url, int total)
0220 {
0221     c4_Row findrow;
0222     d->purl(findrow) = url.toLatin1().constData();
0223     int findidx = d->archiveView.Find(findrow);
0224     if (findidx == -1) {
0225         return;
0226     }
0227     findrow = d->archiveView.GetAt(findidx);
0228     d->ptotalCount(findrow) = total;
0229     d->archiveView.SetAt(findidx, findrow);
0230     markDirty();
0231 }
0232 
0233 QDateTime Akregator::Backend::Storage::lastFetchFor(const QString &url) const
0234 {
0235     c4_Row findrow;
0236     d->purl(findrow) = url.toLatin1().constData();
0237     int findidx = d->archiveView.Find(findrow);
0238 
0239     return findidx != -1 ? QDateTime::fromSecsSinceEpoch(d->plastFetch(d->archiveView.GetAt(findidx))) : QDateTime();
0240 }
0241 
0242 void Akregator::Backend::Storage::setLastFetchFor(const QString &url, const QDateTime &lastFetch)
0243 {
0244     c4_Row findrow;
0245     d->purl(findrow) = url.toLatin1().constData();
0246     int findidx = d->archiveView.Find(findrow);
0247     if (findidx == -1) {
0248         return;
0249     }
0250     findrow = d->archiveView.GetAt(findidx);
0251     d->plastFetch(findrow) = lastFetch.toSecsSinceEpoch();
0252     d->archiveView.SetAt(findidx, findrow);
0253     markDirty();
0254 }
0255 
0256 void Akregator::Backend::Storage::markDirty()
0257 {
0258     if (!d->modified) {
0259         d->modified = true;
0260         // commit changes after 3 seconds
0261         QTimer::singleShot(3s, this, &Storage::slotCommit);
0262     }
0263 }
0264 
0265 void Akregator::Backend::Storage::slotCommit()
0266 {
0267     if (d->modified) {
0268         commit();
0269     }
0270     d->modified = false;
0271 }
0272 
0273 QStringList Akregator::Backend::Storage::feeds() const
0274 {
0275     // TODO: cache list
0276     QStringList list;
0277     const int size = d->archiveView.GetSize();
0278     list.reserve(size);
0279     for (int i = 0; i < size; ++i) {
0280         list += QString::fromLatin1(QByteArray(d->purl(d->archiveView.GetAt(i))));
0281     }
0282     // fill with urls
0283     return list;
0284 }
0285 
0286 void Akregator::Backend::Storage::storeFeedList(const QString &opmlStr)
0287 {
0288     if (d->feedListView.GetSize() == 0) {
0289         c4_Row row;
0290         d->pFeedList(row) = !opmlStr.isEmpty() ? opmlStr.toUtf8().data() : "";
0291         d->feedListView.Add(row);
0292     } else {
0293         c4_Row row = d->feedListView.GetAt(0);
0294         d->pFeedList(row) = !opmlStr.isEmpty() ? opmlStr.toUtf8().data() : "";
0295         d->feedListView.SetAt(0, row);
0296     }
0297     markDirty();
0298 }
0299 
0300 QString Akregator::Backend::Storage::restoreFeedList() const
0301 {
0302     if (d->feedListView.GetSize() == 0) {
0303         return {};
0304     }
0305 
0306     c4_Row row = d->feedListView.GetAt(0);
0307     return QString::fromUtf8(QByteArray(d->pFeedList(row)));
0308 }
0309 
0310 #include "moc_storage.cpp"