1 /*
2     This file is part of Akregator.
3 
4     SPDX-FileCopyrightText: 2005 Stanislav Karchebny <Stanislav.Karchebny@kdemail.net>
5     SPDX-FileCopyrightText: 2005 Frank Osterfeld <osterfeld@kde.org>
6 
7     SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
8 */
9 #include "storagemk4impl.h"
10 #include "feedstoragemk4impl.h"
11 
12 #include <mk4.h>
13 
14 #include <QMap>
15 #include <QString>
16 #include <QStringList>
17 #include <QTimer>
18 
19 #include <QDateTime>
20 #include <QDir>
21 #include <QStandardPaths>
22 #include <chrono>
23 
24 using namespace std::chrono_literals;
25 
26 class Akregator::Backend::StorageMK4Impl::StorageMK4ImplPrivate
27 {
28 public:
StorageMK4ImplPrivate()29     StorageMK4ImplPrivate() : purl("url")
30         , pFeedList("feedList")
31         , punread("unread")
32         , ptotalCount("totalCount")
33         , plastFetch("lastFetch")
34     {
35     }
36 
37     c4_Storage *storage;
38     Akregator::Backend::StorageMK4Impl *q;
39     c4_View archiveView;
40     bool autoCommit = false;
41     bool modified = false;
42     mutable QMap<QString, Akregator::Backend::FeedStorageMK4Impl *> feeds;
43     QStringList feedURLs;
44     c4_StringProp purl, pFeedList;
45     c4_IntProp punread, ptotalCount, plastFetch;
46     QString archivePath;
47 
48     c4_Storage *feedListStorage;
49     c4_View feedListView;
50 
51     Akregator::Backend::FeedStorageMK4Impl *createFeedStorage(const QString &url);
52 };
53 
StorageMK4Impl()54 Akregator::Backend::StorageMK4Impl::StorageMK4Impl() : d(new StorageMK4ImplPrivate)
55 {
56     d->q = this;
57     setArchivePath(QString());
58 }
59 
createFeedStorage(const QString & url)60 Akregator::Backend::FeedStorageMK4Impl *Akregator::Backend::StorageMK4Impl::StorageMK4ImplPrivate::createFeedStorage(const QString &url)
61 {
62     if (!feeds.contains(url)) {
63         Akregator::Backend::FeedStorageMK4Impl *fs = new Akregator::Backend::FeedStorageMK4Impl(url, q);
64         feeds[url] = fs;
65         c4_Row findrow;
66         purl(findrow) = url.toLatin1().constData();
67         int findidx = archiveView.Find(findrow);
68         if (findidx == -1) {
69             punread(findrow) = 0;
70             ptotalCount(findrow) = 0;
71             plastFetch(findrow) = 0;
72             archiveView.Add(findrow);
73             modified = true;
74         }
75     }
76     return feeds[url];
77 }
78 
archiveFor(const QString & url)79 Akregator::Backend::FeedStorage *Akregator::Backend::StorageMK4Impl::archiveFor(const QString &url)
80 {
81     return d->createFeedStorage(url);
82 }
83 
archiveFor(const QString & url) const84 const Akregator::Backend::FeedStorage *Akregator::Backend::StorageMK4Impl::archiveFor(const QString &url) const
85 {
86     return d->createFeedStorage(url);
87 }
88 
setArchivePath(const QString & archivePath)89 void Akregator::Backend::StorageMK4Impl::setArchivePath(const QString &archivePath)
90 {
91     if (archivePath.isNull()) { // if isNull, reset to default
92         d->archivePath = defaultArchivePath();
93     } else {
94         d->archivePath = archivePath;
95     }
96 }
97 
archivePath() const98 QString Akregator::Backend::StorageMK4Impl::archivePath() const
99 {
100     return d->archivePath;
101 }
102 
defaultArchivePath()103 QString Akregator::Backend::StorageMK4Impl::defaultArchivePath()
104 {
105     const QString ret = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/akregator/Archive");
106     QDir().mkpath(ret);
107     return ret;
108 }
109 
~StorageMK4Impl()110 Akregator::Backend::StorageMK4Impl::~StorageMK4Impl()
111 {
112     close();
113 }
114 
initialize(const QStringList &)115 void Akregator::Backend::StorageMK4Impl::initialize(const QStringList &)
116 {
117 }
118 
open(bool autoCommit)119 bool Akregator::Backend::StorageMK4Impl::open(bool autoCommit)
120 {
121     QString filePath = d->archivePath + QLatin1String("/archiveindex.mk4");
122     d->storage = new c4_Storage(filePath.toLocal8Bit().constData(), true);
123     d->archiveView = d->storage->GetAs("archive[url:S,unread:I,totalCount:I,lastFetch:I]");
124     c4_View hash = d->storage->GetAs("archiveHash[_H:I,_R:I]");
125     d->archiveView = d->archiveView.Hash(hash, 1); // hash on url
126     d->autoCommit = autoCommit;
127 
128     filePath = d->archivePath + QLatin1String("/feedlistbackup.mk4");
129     d->feedListStorage = new c4_Storage(filePath.toLocal8Bit().constData(), true);
130     d->feedListView = d->feedListStorage->GetAs("archive[feedList:S,tagSet:S]");
131     return true;
132 }
133 
autoCommit() const134 bool Akregator::Backend::StorageMK4Impl::autoCommit() const
135 {
136     return d->autoCommit;
137 }
138 
close()139 void Akregator::Backend::StorageMK4Impl::close()
140 {
141     QMap<QString, FeedStorageMK4Impl *>::Iterator it;
142     QMap<QString, FeedStorageMK4Impl *>::Iterator end(d->feeds.end());
143     for (it = d->feeds.begin(); it != end; ++it) {
144         it.value()->close();
145         delete it.value();
146     }
147     if (d->autoCommit) {
148         d->storage->Commit();
149     }
150 
151     delete d->storage;
152     d->storage = nullptr;
153 
154     d->feedListStorage->Commit();
155     delete d->feedListStorage;
156     d->feedListStorage = nullptr;
157 }
158 
commit()159 bool Akregator::Backend::StorageMK4Impl::commit()
160 {
161     QMap<QString, FeedStorageMK4Impl *>::Iterator it;
162     QMap<QString, FeedStorageMK4Impl *>::Iterator end(d->feeds.end());
163     for (it = d->feeds.begin(); it != end; ++it) {
164         it.value()->commit();
165     }
166 
167     if (d->storage) {
168         d->storage->Commit();
169         return true;
170     }
171 
172     return false;
173 }
174 
rollback()175 bool Akregator::Backend::StorageMK4Impl::rollback()
176 {
177     QMap<QString, FeedStorageMK4Impl *>::Iterator it;
178     QMap<QString, FeedStorageMK4Impl *>::Iterator end(d->feeds.end());
179     for (it = d->feeds.begin(); it != end; ++it) {
180         it.value()->rollback();
181     }
182 
183     if (d->storage) {
184         d->storage->Rollback();
185         return true;
186     }
187     return false;
188 }
189 
unreadFor(const QString & url) const190 int Akregator::Backend::StorageMK4Impl::unreadFor(const QString &url) const
191 {
192     c4_Row findrow;
193     d->purl(findrow) = url.toLatin1().constData();
194     int findidx = d->archiveView.Find(findrow);
195 
196     return findidx != -1 ? d->punread(d->archiveView.GetAt(findidx)) : 0;
197 }
198 
setUnreadFor(const QString & url,int unread)199 void Akregator::Backend::StorageMK4Impl::setUnreadFor(const QString &url, int unread)
200 {
201     c4_Row findrow;
202     d->purl(findrow) = url.toLatin1().constData();
203     int findidx = d->archiveView.Find(findrow);
204     if (findidx == -1) {
205         return;
206     }
207     findrow = d->archiveView.GetAt(findidx);
208     d->punread(findrow) = unread;
209     d->archiveView.SetAt(findidx, findrow);
210     markDirty();
211 }
212 
totalCountFor(const QString & url) const213 int Akregator::Backend::StorageMK4Impl::totalCountFor(const QString &url) const
214 {
215     c4_Row findrow;
216     d->purl(findrow) = url.toLatin1().constData();
217     int findidx = d->archiveView.Find(findrow);
218 
219     return findidx != -1 ? d->ptotalCount(d->archiveView.GetAt(findidx)) : 0;
220 }
221 
setTotalCountFor(const QString & url,int total)222 void Akregator::Backend::StorageMK4Impl::setTotalCountFor(const QString &url, int total)
223 {
224     c4_Row findrow;
225     d->purl(findrow) = url.toLatin1().constData();
226     int findidx = d->archiveView.Find(findrow);
227     if (findidx == -1) {
228         return;
229     }
230     findrow = d->archiveView.GetAt(findidx);
231     d->ptotalCount(findrow) = total;
232     d->archiveView.SetAt(findidx, findrow);
233     markDirty();
234 }
235 
lastFetchFor(const QString & url) const236 QDateTime Akregator::Backend::StorageMK4Impl::lastFetchFor(const QString &url) const
237 {
238     c4_Row findrow;
239     d->purl(findrow) = url.toLatin1().constData();
240     int findidx = d->archiveView.Find(findrow);
241 
242     return findidx != -1 ? QDateTime::fromSecsSinceEpoch(d->plastFetch(d->archiveView.GetAt(findidx))) : QDateTime();
243 }
244 
setLastFetchFor(const QString & url,const QDateTime & lastFetch)245 void Akregator::Backend::StorageMK4Impl::setLastFetchFor(const QString &url, const QDateTime &lastFetch)
246 {
247     c4_Row findrow;
248     d->purl(findrow) = url.toLatin1().constData();
249     int findidx = d->archiveView.Find(findrow);
250     if (findidx == -1) {
251         return;
252     }
253     findrow = d->archiveView.GetAt(findidx);
254     d->plastFetch(findrow) = lastFetch.toSecsSinceEpoch();
255     d->archiveView.SetAt(findidx, findrow);
256     markDirty();
257 }
258 
markDirty()259 void Akregator::Backend::StorageMK4Impl::markDirty()
260 {
261     if (!d->modified) {
262         d->modified = true;
263         // commit changes after 3 seconds
264         QTimer::singleShot(3s, this, &StorageMK4Impl::slotCommit);
265     }
266 }
267 
slotCommit()268 void Akregator::Backend::StorageMK4Impl::slotCommit()
269 {
270     if (d->modified) {
271         commit();
272     }
273     d->modified = false;
274 }
275 
feeds() const276 QStringList Akregator::Backend::StorageMK4Impl::feeds() const
277 {
278     // TODO: cache list
279     QStringList list;
280     const int size = d->archiveView.GetSize();
281     list.reserve(size);
282     for (int i = 0; i < size; ++i) {
283         list += QString::fromLatin1(d->purl(d->archiveView.GetAt(i)));
284     }
285     // fill with urls
286     return list;
287 }
288 
storeFeedList(const QString & opmlStr)289 void Akregator::Backend::StorageMK4Impl::storeFeedList(const QString &opmlStr)
290 {
291     if (d->feedListView.GetSize() == 0) {
292         c4_Row row;
293         d->pFeedList(row) = !opmlStr.isEmpty() ? opmlStr.toUtf8().data() : "";
294         d->feedListView.Add(row);
295     } else {
296         c4_Row row = d->feedListView.GetAt(0);
297         d->pFeedList(row) = !opmlStr.isEmpty() ? opmlStr.toUtf8().data() : "";
298         d->feedListView.SetAt(0, row);
299     }
300     markDirty();
301 }
302 
restoreFeedList() const303 QString Akregator::Backend::StorageMK4Impl::restoreFeedList() const
304 {
305     if (d->feedListView.GetSize() == 0) {
306         return QString();
307     }
308 
309     c4_Row row = d->feedListView.GetAt(0);
310     return QString::fromUtf8(d->pFeedList(row));
311 }
312