Lomiri
Loading...
Searching...
No Matches
windowstatestorage.cpp
1/*
2 * Copyright 2015-2016 Canonical Ltd.
3 * Copyright 2021 UBports Foundation
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation; version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "windowstatestorage.h"
19
20#include <QDebug>
21#include <QDir>
22#include <QMetaObject>
23#include <QObject>
24#include <QSqlQuery>
25#include <QSqlError>
26#include <QSqlResult>
27#include <QStandardPaths>
28#include <QRect>
29#include <lomiri/shell/application/ApplicationInfoInterface.h>
30
31
32class AsyncQuery: public QObject
33{
34 Q_OBJECT
35
36public:
37 AsyncQuery(const QString &dbName):
38 m_dbName(dbName)
39 {
40 }
41
42 ~AsyncQuery()
43 {
44 QSqlDatabase::removeDatabase(m_connectionName);
45 }
46
47 Q_INVOKABLE const QString getDbName()
48 {
49 if (!m_ok) {
50 return "ERROR";
51 }
52 QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
53 return connection.databaseName();
54 }
55
56 Q_INVOKABLE bool initdb()
57 {
58 if (m_ok) {
59 return true;
60 }
61 QSqlDatabase connection = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), m_connectionName);
62 connection.setDatabaseName(m_dbName);
63 connection.setConnectOptions(QStringLiteral("QSQLITE_BUSY_TIMEOUT=1000"));
64 if (!connection.open()) {
65 qWarning() << "AsyncQuery::initdb: Error opening state database. Window positions will not be saved or restored." << m_dbName << connection.lastError().driverText() << connection.lastError().databaseText();
66 return false;
67 }
68 QSqlQuery query(connection);
69
70 if (!connection.tables().contains(QStringLiteral("geometry"))) {
71 QString geometryQuery = QStringLiteral("CREATE TABLE geometry(windowId TEXT UNIQUE, x INTEGER, y INTEGER, width INTEGER, height INTEGER);");
72 if (!query.exec(geometryQuery)) {
73 logSqlError(query);
74 return false;
75 }
76 }
77
78 if (!connection.tables().contains(QStringLiteral("state"))) {
79 QString stateQuery = QStringLiteral("CREATE TABLE state(windowId TEXT UNIQUE, state INTEGER);");
80 if (!query.exec(stateQuery)) {
81 logSqlError(query);
82 return false;
83 }
84 }
85
86 if (!connection.tables().contains(QStringLiteral("stage"))) {
87 QString stageQuery = QStringLiteral("CREATE TABLE stage(appId TEXT UNIQUE, stage INTEGER);");
88 if (!query.exec(stageQuery)) {
89 logSqlError(query);
90 return false;
91 }
92 }
93 m_ok = true;
94 return true;
95 }
96
97 Q_INVOKABLE int getState(const QString &windowId) const
98 {
99 if (!m_ok) {
100 return -1;
101 }
102 QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
103 QSqlQuery query(connection);
104 query.prepare(m_getStateQuery);
105 query.bindValue(":windowId", windowId);
106 query.exec();
107 if (!query.isActive() || !query.isSelect()) {
108 logSqlError(query);
109 return -1;
110 }
111 if (!query.first()) {
112 return -1;
113 }
114 bool converted = false;
115 QVariant resultStr = query.value(0);
116 int result = resultStr.toInt(&converted);
117 if (converted) {
118 return result;
119 } else {
120 qWarning() << "getState result expected integer, got " << resultStr;
121 return -1;
122 }
123 }
124
125 Q_INVOKABLE QRect getGeometry(const QString &windowId) const
126 {
127 if (!m_ok) {
128 return QRect();
129 }
130 QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
131 QSqlQuery query(connection);
132 query.prepare(m_getGeometryQuery);
133 query.bindValue(":windowId", windowId);
134 query.exec();
135 if (!query.isActive() || !query.isSelect()) {
136 logSqlError(query);
137 return QRect();
138 }
139
140 if (!query.first()) {
141 return QRect();
142 }
143
144 bool xConverted, yConverted, widthConverted, heightConverted = false;
145 int x, y, width, height;
146 QVariant xResultStr = query.value(QStringLiteral("x"));
147 QVariant yResultStr = query.value(QStringLiteral("y"));
148 QVariant widthResultStr = query.value(QStringLiteral("width"));
149 QVariant heightResultStr = query.value(QStringLiteral("height"));
150 x = xResultStr.toInt(&xConverted);
151 y = yResultStr.toInt(&yConverted);
152 width = widthResultStr.toInt(&widthConverted);
153 height = heightResultStr.toInt(&heightConverted);
154
155 if (xConverted && yConverted && widthConverted && heightConverted) {
156 return QRect(x, y, width, height);
157 } else {
158 qWarning() << "getGeometry result expected integers, got x:"
159 << xResultStr << "y:" << yResultStr << "width" << widthResultStr
160 << "height:" << heightResultStr;
161 return QRect();
162 }
163
164 }
165
166 Q_INVOKABLE int getStage(const QString &appId) const
167 {
168 if (!m_ok) {
169 return -1;
170 }
171 QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
172 QSqlQuery query(connection);
173 query.prepare(m_getStageQuery);
174 query.bindValue(":appId", appId);
175 query.exec();
176 if (!query.isActive() || !query.isSelect()) {
177 logSqlError(query);
178 return -1;
179 }
180 if (!query.first()) {
181 return -1;
182 }
183 bool converted = false;
184 QVariant resultStr = query.value(0);
185 int result = resultStr.toInt(&converted);
186 if (converted) {
187 return result;
188 } else {
189 qWarning() << "getStage result expected integer, got " << resultStr;
190 return -1;
191 }
192 }
193
194public Q_SLOTS:
195
196 void saveState(const QString &windowId, Mir::State state)
197 {
198 if (!m_ok) {
199 return;
200 }
201 QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
202 QSqlQuery query(connection);
203 query.prepare(m_saveStateQuery);
204 query.bindValue(":windowId", windowId);
205 query.bindValue(":state", (int)state);
206 if (!query.exec()) {
207 logSqlError(query);
208 }
209 }
210
211 void saveGeometry(const QString &windowId, const QRect &rect)
212 {
213 if (!m_ok) {
214 return;
215 }
216 QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
217 QSqlQuery query(connection);
218 query.prepare(m_saveGeometryQuery);
219 query.bindValue(":windowId", windowId);
220 query.bindValue(":x", rect.x());
221 query.bindValue(":y", rect.y());
222 query.bindValue(":width", rect.width());
223 query.bindValue(":height", rect.height());
224 if (!query.exec()) {
225 logSqlError(query);
226 }
227 }
228
229 void saveStage(const QString &appId, int stage)
230 {
231 if (!m_ok) {
232 return;
233 }
234 QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
235 QSqlQuery query(connection);
236 query.prepare(m_saveStageQuery);
237 query.bindValue(":appId", appId);
238 query.bindValue(":stage", stage);
239 if (!query.exec()) {
240 logSqlError(query);
241 }
242 }
243
244private:
245 static const QString m_connectionName;
246 static const QString m_getStateQuery;
247 static const QString m_saveStateQuery;
248 static const QString m_getGeometryQuery;
249 static const QString m_saveGeometryQuery;
250 static const QString m_getStageQuery;
251 static const QString m_saveStageQuery;
252
253 static void logSqlError(const QSqlQuery query)
254 {
255 qWarning() << "Error executing query" << query.lastQuery()
256 << "Driver error:" << query.lastError().driverText()
257 << "Database error:" << query.lastError().databaseText();
258 }
259
260 QString m_dbName;
261 bool m_ok = false;
262};
263
264const QString AsyncQuery::m_connectionName = QStringLiteral("WindowStateStorage");
265const QString AsyncQuery::m_getStateQuery = QStringLiteral("SELECT state FROM state WHERE windowId = :windowId");
266const QString AsyncQuery::m_saveStateQuery = QStringLiteral("INSERT OR REPLACE INTO state (windowId, state) values (:windowId, :state)");
267const QString AsyncQuery::m_getGeometryQuery = QStringLiteral("SELECT * FROM geometry WHERE windowId = :windowId");
268const QString AsyncQuery::m_saveGeometryQuery = QStringLiteral("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values (:windowId, :x, :y, :width, :height)");
269const QString AsyncQuery::m_getStageQuery = QStringLiteral("SELECT stage FROM stage WHERE appId = :appId");
270const QString AsyncQuery::m_saveStageQuery = QStringLiteral("INSERT OR REPLACE INTO stage (appId, stage) values (:appId, :stage)");
271
272WindowStateStorage::WindowStateStorage(const QString &dbName, QObject *parent):
273 QObject(parent),
274 m_thread()
275{
276 QString dbFile;
277 if (dbName.isEmpty()) {
278 const QString dbPath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/lomiri/");
279 QDir dir;
280 dir.mkpath(dbPath);
281 dbFile = QString(dbPath) + QStringLiteral("windowstatestorage.sqlite");
282 } else {
283 dbFile = dbName;
284 }
285 m_asyncQuery = new AsyncQuery(dbFile);
286 m_asyncQuery->moveToThread(&m_thread);
287 connect(&m_thread, &QThread::finished, m_asyncQuery, &QObject::deleteLater);
288 m_thread.start();
289 // Note that we're relying on initdb being called before any other methods
290 // on AsyncQuery. Given the current behavior of QueuedConnection (slots
291 // invoked in the order they are received), this is fine.
292 QMetaObject::invokeMethod(m_asyncQuery, "initdb",
293 Qt::QueuedConnection);
294 connect(this, &WindowStateStorage::saveState, m_asyncQuery, &AsyncQuery::saveState);
295 connect(this, &WindowStateStorage::saveGeometry, m_asyncQuery, &AsyncQuery::saveGeometry);
296 connect(this, &WindowStateStorage::saveStage, m_asyncQuery, &AsyncQuery::saveStage);
297}
298
299WindowStateStorage::~WindowStateStorage()
300{
301 m_thread.quit();
302 m_thread.wait();
303}
304
305Mir::State WindowStateStorage::getState(const QString &windowId, Mir::State defaultValue) const
306{
307 int state;
308
309 QMetaObject::invokeMethod(m_asyncQuery, "getState", Qt::BlockingQueuedConnection,
310 Q_RETURN_ARG(int, state),
311 Q_ARG(const QString&, windowId)
312 );
313
314 if (state == -1) {
315 return defaultValue;
316 }
317
318 return (Mir::State)state;
319}
320
321int WindowStateStorage::getStage(const QString &appId, int defaultValue) const
322{
323 int stage;
324
325 QMetaObject::invokeMethod(m_asyncQuery, "getStage", Qt::BlockingQueuedConnection,
326 Q_RETURN_ARG(int, stage),
327 Q_ARG(const QString&, appId)
328 );
329
330 if (stage == -1) {
331 return defaultValue;
332 }
333
334 return stage;
335}
336
337QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue) const
338{
339 QRect geometry;
340 QMetaObject::invokeMethod(m_asyncQuery, "getGeometry", Qt::BlockingQueuedConnection,
341 Q_RETURN_ARG(QRect, geometry),
342 Q_ARG(const QString&, windowId)
343 );
344 if (geometry.isNull() || !geometry.isValid()) {
345 return defaultValue;
346 }
347 return geometry;
348}
349
350const QString WindowStateStorage::getDbName()
351{
352 QString dbName;
353 QMetaObject::invokeMethod(m_asyncQuery, "getDbName", Qt::BlockingQueuedConnection,
354 Q_RETURN_ARG(QString, dbName)
355 );
356 return QString(dbName);
357}
358
359#include "windowstatestorage.moc"