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