1 /*
2     SPDX-FileCopyrightText: 2011 Marco Martin <mart@kde.org>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "storagethread_p.h"
8 
9 #include <QCoreApplication>
10 #include <QDataStream>
11 #include <QDir>
12 #include <QSqlDriver>
13 #include <QSqlError>
14 #include <QSqlField>
15 #include <QSqlQuery>
16 #include <QSqlRecord>
17 
18 #include "debug_p.h"
19 #include <QDebug>
20 #include <QStandardPaths>
21 
22 namespace Plasma
23 {
24 class StorageThreadSingleton
25 {
26 public:
StorageThreadSingleton()27     StorageThreadSingleton()
28     {
29     }
30 
31     StorageThread self;
32 };
33 
Q_GLOBAL_STATIC(StorageThreadSingleton,privateStorageThreadSelf)34 Q_GLOBAL_STATIC(StorageThreadSingleton, privateStorageThreadSelf)
35 
36 static void closeConnection()
37 {
38     StorageThread::self()->closeDb();
39     StorageThread::self()->quit();
40 }
41 
StorageThread(QObject * parent)42 StorageThread::StorageThread(QObject *parent)
43     : QThread(parent)
44 {
45     qAddPostRoutine(closeConnection);
46 }
47 
~StorageThread()48 StorageThread::~StorageThread()
49 {
50 }
51 
self()52 Plasma::StorageThread *StorageThread::self()
53 {
54     return &privateStorageThreadSelf()->self;
55 }
56 
closeDb()57 void StorageThread::closeDb()
58 {
59     QString name = m_db.connectionName();
60     QSqlDatabase::removeDatabase(name);
61     m_db = QSqlDatabase();
62 }
63 
initializeDb(StorageJob * caller)64 void StorageThread::initializeDb(StorageJob *caller)
65 {
66     if (!m_db.open()) {
67         m_db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), QStringLiteral("plasma-storage-%1").arg((quintptr)this));
68         const QString storageDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
69         QDir().mkpath(storageDir);
70         m_db.setDatabaseName(storageDir + QLatin1Char('/') + QStringLiteral("plasma-storage2.db"));
71     }
72 
73     if (!m_db.open()) {
74         qCWarning(LOG_PLASMA) << "Unable to open the plasma storage cache database: " << m_db.lastError();
75     } else if (!m_db.tables().contains(caller->clientName())) {
76         QSqlQuery query(m_db);
77         query.prepare(QStringLiteral("create table ") + caller->clientName()
78                       + QStringLiteral(" (valueGroup varchar(256), id varchar(256), txt TEXT, int INTEGER, float REAL, binary BLOB, creationTime datetime, "
79                                        "accessTime datetime, primary key (valueGroup, id))"));
80         if (!query.exec()) {
81             qCWarning(LOG_PLASMA) << "Unable to create table for" << caller->clientName();
82             m_db.close();
83         }
84     }
85     m_db.transaction();
86 }
87 
save(QPointer<StorageJob> wcaller,const QVariantMap & params)88 void StorageThread::save(QPointer<StorageJob> wcaller, const QVariantMap &params)
89 {
90     StorageJob *caller = wcaller.data();
91     if (!caller) {
92         return;
93     }
94 
95     initializeDb(caller);
96     QString valueGroup = params[QStringLiteral("group")].toString();
97     if (valueGroup.isEmpty()) {
98         valueGroup = QStringLiteral("default");
99     }
100     QSqlQuery query(m_db);
101 
102     QVariantMap data = caller->data();
103     if (params.value(QStringLiteral("key")).toString().isNull()) {
104         data.insert(params.value(QStringLiteral("key")).toString(), params.value(QStringLiteral("data")));
105     }
106     caller->setData(data);
107 
108     QMapIterator<QString, QVariant> it(caller->data());
109 
110     QString ids;
111     while (it.hasNext()) {
112         it.next();
113         QSqlField field(QStringLiteral(":id"), QVariant::String);
114         field.setValue(it.key());
115         if (!ids.isEmpty()) {
116             ids.append(QStringLiteral(", "));
117         }
118         ids.append(m_db.driver()->formatValue(field));
119     }
120 
121     query.prepare(QStringLiteral("delete from ") + caller->clientName() + QStringLiteral(" where valueGroup = :valueGroup and id in (") + ids
122                   + QStringLiteral(");"));
123     query.bindValue(QStringLiteral(":valueGroup"), valueGroup);
124 
125     if (!query.exec()) {
126         m_db.commit();
127         Q_EMIT newResult(caller, false);
128         return;
129     }
130 
131     query.prepare(QStringLiteral("insert into ") + caller->clientName()
132                   + QStringLiteral(" values(:valueGroup, :id, :txt, :int, :float, :binary, date('now'), date('now'))"));
133     query.bindValue(QStringLiteral(":valueGroup"), valueGroup);
134     query.bindValue(QStringLiteral(":txt"), QVariant());
135     query.bindValue(QStringLiteral(":int"), QVariant());
136     query.bindValue(QStringLiteral(":float"), QVariant());
137     query.bindValue(QStringLiteral(":binary"), QVariant());
138 
139     const QString key = params.value(QStringLiteral("key")).toString();
140     if (!key.isEmpty()) {
141         QVariantMap data = caller->data();
142         data.insert(key, params[QStringLiteral("data")]);
143         caller->setData(data);
144     }
145 
146     it.toFront();
147     while (it.hasNext()) {
148         it.next();
149         // qCDebug(LOG_PLASMA) << "going to insert" << valueGroup << it.key();
150         query.bindValue(QStringLiteral(":id"), it.key());
151 
152         QString field;
153         bool binary = false;
154         switch (it.value().type()) {
155         case QVariant::String:
156             field = QStringLiteral(":txt");
157             break;
158         case QVariant::Int:
159             field = QStringLiteral(":int");
160             break;
161         case QVariant::Double:
162             field = QStringLiteral(":float");
163             break;
164         case QVariant::ByteArray:
165             binary = true;
166             field = QStringLiteral(":binary");
167             break;
168         default:
169             continue;
170         }
171 
172         if (binary) {
173             QByteArray b;
174             QDataStream ds(&b, QIODevice::WriteOnly);
175             ds << it.value();
176             query.bindValue(field, b);
177         } else {
178             query.bindValue(field, it.value());
179         }
180 
181         if (!query.exec()) {
182             // qCDebug(LOG_PLASMA) << "query failed:" << query.lastQuery() << query.lastError().text();
183             m_db.commit();
184             Q_EMIT newResult(caller, false);
185             return;
186         }
187 
188         query.bindValue(field, QVariant());
189     }
190     m_db.commit();
191 
192     Q_EMIT newResult(caller, true);
193 }
194 
retrieve(QPointer<StorageJob> wcaller,const QVariantMap & params)195 void StorageThread::retrieve(QPointer<StorageJob> wcaller, const QVariantMap &params)
196 {
197     StorageJob *caller = wcaller.data();
198     if (!caller) {
199         return;
200     }
201 
202     const QString clientName = caller->clientName();
203     initializeDb(caller);
204     QString valueGroup = params[QStringLiteral("group")].toString();
205     if (valueGroup.isEmpty()) {
206         valueGroup = QStringLiteral("default");
207     }
208 
209     QSqlQuery query(m_db);
210 
211     // a bit redundant but should be the faster way with less string concatenation as possible
212     if (params[QStringLiteral("key")].toString().isEmpty()) {
213         // update modification time
214         query.prepare(QStringLiteral("update ") + clientName + QStringLiteral(" set accessTime=date('now') where valueGroup=:valueGroup"));
215         query.bindValue(QStringLiteral(":valueGroup"), valueGroup);
216         query.exec();
217 
218         query.prepare(QStringLiteral("select * from ") + clientName + QStringLiteral(" where valueGroup=:valueGroup"));
219         query.bindValue(QStringLiteral(":valueGroup"), valueGroup);
220     } else {
221         // update modification time
222         query.prepare(QStringLiteral("update ") + clientName + QStringLiteral(" set accessTime=date('now') where valueGroup=:valueGroup and id=:key"));
223         query.bindValue(QStringLiteral(":valueGroup"), valueGroup);
224         query.bindValue(QStringLiteral(":key"), params[QStringLiteral("key")].toString());
225         query.exec();
226 
227         query.prepare(QStringLiteral("select * from ") + clientName + QStringLiteral(" where valueGroup=:valueGroup and id=:key"));
228         query.bindValue(QStringLiteral(":valueGroup"), valueGroup);
229         query.bindValue(QStringLiteral(":key"), params[QStringLiteral("key")].toString());
230     }
231 
232     const bool success = query.exec();
233 
234     QVariant result;
235 
236     if (success) {
237         QSqlRecord rec = query.record();
238         const int keyColumn = rec.indexOf(QLatin1String("id"));
239         const int textColumn = rec.indexOf(QLatin1String("txt"));
240         const int intColumn = rec.indexOf(QLatin1String("int"));
241         const int floatColumn = rec.indexOf(QLatin1String("float"));
242         const int binaryColumn = rec.indexOf(QLatin1String("binary"));
243 
244         QVariantMap data;
245         while (query.next()) {
246             const QString key = query.value(keyColumn).toString();
247             if (!query.value(textColumn).isNull()) {
248                 data.insert(key, query.value(textColumn));
249             } else if (!query.value(intColumn).isNull()) {
250                 data.insert(key, query.value(intColumn));
251             } else if (!query.value(floatColumn).isNull()) {
252                 data.insert(key, query.value(floatColumn));
253             } else if (!query.value(binaryColumn).isNull()) {
254                 QByteArray bytes = query.value(binaryColumn).toByteArray();
255                 QDataStream in(bytes);
256                 QVariant v;
257                 in >> v;
258                 data.insert(key, v);
259             }
260         }
261         result = data;
262     } else {
263         result = false;
264     }
265 
266     Q_EMIT newResult(caller, result);
267 }
268 
deleteEntry(QPointer<StorageJob> wcaller,const QVariantMap & params)269 void StorageThread::deleteEntry(QPointer<StorageJob> wcaller, const QVariantMap &params)
270 {
271     StorageJob *caller = wcaller.data();
272     if (!caller) {
273         return;
274     }
275 
276     initializeDb(caller);
277     QString valueGroup = params[QStringLiteral("group")].toString();
278     if (valueGroup.isEmpty()) {
279         valueGroup = QStringLiteral("default");
280     }
281 
282     QSqlQuery query(m_db);
283 
284     if (params[QStringLiteral("key")].toString().isEmpty()) {
285         query.prepare(QStringLiteral("delete from ") + caller->clientName() + QStringLiteral(" where valueGroup=:valueGroup"));
286         query.bindValue(QStringLiteral(":valueGroup"), valueGroup);
287     } else {
288         query.prepare(QStringLiteral("delete from ") + caller->clientName() + QStringLiteral(" where valueGroup=:valueGroup and id=:key"));
289         query.bindValue(QStringLiteral(":valueGroup"), valueGroup);
290         query.bindValue(QStringLiteral(":key"), params[QStringLiteral("key")].toString());
291     }
292 
293     const bool success = query.exec();
294     m_db.commit();
295 
296     Q_EMIT newResult(caller, success);
297 }
298 
expire(QPointer<StorageJob> wcaller,const QVariantMap & params)299 void StorageThread::expire(QPointer<StorageJob> wcaller, const QVariantMap &params)
300 {
301     StorageJob *caller = wcaller.data();
302     if (!caller) {
303         return;
304     }
305 
306     initializeDb(caller);
307     QString valueGroup = params[QStringLiteral("group")].toString();
308     if (valueGroup.isEmpty()) {
309         valueGroup = QStringLiteral("default");
310     }
311 
312     QSqlQuery query(m_db);
313     if (valueGroup.isEmpty()) {
314         query.prepare(QStringLiteral("delete from ") + caller->clientName() + QStringLiteral(" where accessTime < :date"));
315         QDateTime time(QDateTime::currentDateTime().addSecs(-params[QStringLiteral("age")].toUInt()));
316         query.bindValue(QStringLiteral(":date"), time.toSecsSinceEpoch());
317     } else {
318         query.prepare(QStringLiteral("delete from ") + caller->clientName() + QStringLiteral(" where valueGroup=:valueGroup and accessTime < :date"));
319         query.bindValue(QStringLiteral(":valueGroup"), valueGroup);
320         QDateTime time(QDateTime::currentDateTime().addSecs(-params[QStringLiteral("age")].toUInt()));
321         query.bindValue(QStringLiteral(":date"), time.toSecsSinceEpoch());
322     }
323 
324     const bool success = query.exec();
325 
326     Q_EMIT newResult(caller, success);
327 }
328 
run()329 void StorageThread::run()
330 {
331     exec();
332 }
333 
334 }
335 
336 #include "moc_storagethread_p.cpp"
337