@@ -1,5 +1,6 @@
11/*
22 * Copyright 2015-2016 Canonical Ltd.
3+ * Copyright 2021 UBports Foundation
34 *
45 * This program is free software; you can redistribute it and/or modify
56 * it under the terms of the GNU Lesser General Public License as published by
@@ -16,167 +17,319 @@
1617
1718#include "windowstatestorage.h"
1819
19−#include <QtConcurrent>
2020#include <QDebug>
21+#include <QDir>
22+#include <QMetaObject>
23+#include <QObject>
2124#include <QSqlQuery>
2225#include <QSqlError>
2326#include <QSqlResult>
27+#include <QStandardPaths>
2428#include <QRect>
2529#include <unity/shell/application/ApplicationInfoInterface.h>
2630
27−QMutex WindowStateStorage::s_mutex;
31+AsyncQuery::AsyncQuery(const QString& dbName):
32+ m_dbName(dbName)
33+{
34+}
2835
29−inline QString sanitiseString(QString string) {
30− return string.remove(QLatin1Char('\"'))
31− .remove(QLatin1Char('\''))
32− .remove(QLatin1Char('\\'));
36+AsyncQuery::~AsyncQuery()
37+{
38+ QSqlDatabase::removeDatabase(m_connectionName);
3339}
3440
35−WindowStateStorage::WindowStateStorage(const QString& dbName, QObject *parent):
36− QObject(parent)
37−{
38− const QString dbPath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/unity8/");
39− m_db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"));
40− QDir dir;
41− dir.mkpath(dbPath);
42− if (dbName != nullptr) {
43− m_db.setDatabaseName(dbName);
44− } else {
45− m_db.setDatabaseName(dbPath + "windowstatestorage.sqlite");
41+bool AsyncQuery::initdb()
42+{
43+ QSqlDatabase connection = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), m_connectionName);
44+ connection.setDatabaseName(m_dbName);
45+ if (!connection.open()) {
46+ qWarning() << "Error opening state database " << m_dbName << connection.lastError().driverText() << connection.lastError().databaseText();
47+ return false;
48+ }
49+ QSqlQuery query(connection);
50+
51+ if (!connection.tables().contains(QStringLiteral("geometry"))) {
52+ QString geometryQuery = QStringLiteral("CREATE TABLE geometry(windowId TEXT UNIQUE, x INTEGER, y INTEGER, width INTEGER, height INTEGER);");
53+ if (!query.exec(geometryQuery)) {
54+ logSqlError(query);
55+ return false;
56+ }
4657 }
47− initdb();
58+
59+ if (!connection.tables().contains(QStringLiteral("state"))) {
60+ QString stateQuery = QStringLiteral("CREATE TABLE state(windowId TEXT UNIQUE, state INTEGER);");
61+ if (!query.exec(stateQuery)) {
62+ logSqlError(query);
63+ return false;
64+ }
65+ }
66+
67+ if (!connection.tables().contains(QStringLiteral("stage"))) {
68+ QString stageQuery = QStringLiteral("CREATE TABLE stage(appId TEXT UNIQUE, stage INTEGER);");
69+ if (!query.exec(stageQuery)) {
70+ logSqlError(query);
71+ return false;
72+ }
73+ }
74+ return true;
4875}
4976
50−WindowStateStorage::~WindowStateStorage()
77+void AsyncQuery::saveState(const QString &windowId, int state)
5178{
52− m_db.close();
79+ QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
80+ QSqlQuery query(connection);
81+ query.prepare(m_saveStateQuery);
82+ query.bindValue(":windowId", windowId);
83+ query.bindValue(":state", state);
84+ if (!query.exec()) {
85+ logSqlError(query);
86+ }
5387}
5488
55−void WindowStateStorage::saveState(const QString &windowId, WindowStateStorage::WindowState state)
89+int AsyncQuery::getState(const QString &windowId) const
5690{
57− const QString queryString = QStringLiteral("INSERT OR REPLACE INTO state (windowId, state) values ('%1', '%2');")
58− .arg(sanitiseString(windowId))
59− .arg((int)state);
60−
61− saveValue(queryString);
91+ QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
92+ QSqlQuery query(connection);
93+ query.prepare(m_getStateQuery);
94+ query.bindValue(":windowId", windowId);
95+ query.exec();
96+ if (!query.isActive() || !query.isSelect()) {
97+ logSqlError(query);
98+ return -1;
99+ }
100+ if (!query.first()) {
101+ return -1;
102+ }
103+ bool converted = false;
104+ QVariant resultStr = query.value(0);
105+ int result = resultStr.toInt(&converted);
106+ if (converted) {
107+ return result;
108+ } else {
109+ qWarning() << "getState result expected integer, got " << resultStr;
110+ return -1;
111+ }
62112}
63113
64−WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue) const
114+void AsyncQuery::saveGeometry(const QString &windowId, const QRect &rect)
65115{
66− const QString queryString = QStringLiteral("SELECT state FROM state WHERE windowId = '%1';")
67− .arg(sanitiseString(windowId));
116+ QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
117+ QSqlQuery query(connection);
118+ query.prepare(m_saveGeometryQuery);
119+ query.bindValue(":windowId", windowId);
120+ query.bindValue(":x", rect.x());
121+ query.bindValue(":y", rect.y());
122+ query.bindValue(":width", rect.width());
123+ query.bindValue(":height", rect.height());
124+ if (!query.exec()) {
125+ logSqlError(query);
126+ }
127+}
68128
69− QSqlQuery query = getValue(queryString);
129+QRect AsyncQuery::getGeometry(const QString &windowId) const
130+{
131+ QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
132+ QSqlQuery query(connection);
133+ query.prepare(m_getGeometryQuery);
134+ query.bindValue(":windowId", windowId);
135+ query.exec();
136+ if (!query.isActive() || !query.isSelect()) {
137+ logSqlError(query);
138+ return QRect();
139+ }
70140
71141 if (!query.first()) {
72− return defaultValue;
142+ return QRect();
73143 }
74− return (WindowState)query.value(QStringLiteral("state")).toInt();
75−}
76144
77−void WindowStateStorage::saveGeometry(const QString &windowId, const QRect &rect)
78−{
79− const QString queryString = QStringLiteral("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values ('%1', '%2', '%3', '%4', '%5');")
80− .arg(sanitiseString(windowId))
81− .arg(rect.x())
82− .arg(rect.y())
83− .arg(rect.width())
84− .arg(rect.height());
145+ bool xConverted, yConverted, widthConverted, heightConverted = false;
146+ int x, y, width, height;
147+ QVariant xResultStr = query.value(QStringLiteral("x"));
148+ QVariant yResultStr = query.value(QStringLiteral("y"));
149+ QVariant widthResultStr = query.value(QStringLiteral("width"));
150+ QVariant heightResultStr = query.value(QStringLiteral("height"));
151+ x = xResultStr.toInt(&xConverted);
152+ y = yResultStr.toInt(&yConverted);
153+ width = widthResultStr.toInt(&widthConverted);
154+ height = heightResultStr.toInt(&heightConverted);
155+
156+ if (xConverted && yConverted && widthConverted && heightConverted) {
157+ return QRect(x, y, width, height);
158+ } else {
159+ qWarning() << "getGeometry result expected integers, got x:"
160+ << xResultStr << "y:" << yResultStr << "width" << widthResultStr
161+ << "height:" << heightResultStr;
162+ return QRect();
163+ }
85164
86− saveValue(queryString);
87165}
88166
89−void WindowStateStorage::saveStage(const QString &appId, int stage)
167+void AsyncQuery::saveStage(const QString &appId, int stage)
90168{
91− const QString queryString = QStringLiteral("INSERT OR REPLACE INTO stage (appId, stage) values ('%1', '%2');")
92− .arg(sanitiseString(appId))
93− .arg(stage);
169+ QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
170+ QSqlQuery query(connection);
171+ query.prepare(m_saveStageQuery);
172+ query.bindValue(":appId", appId);
173+ query.bindValue(":stage", stage);
174+ if (!query.exec()) {
175+ logSqlError(query);
176+ }
177+}
94178
95− saveValue(queryString);
179+int AsyncQuery::getStage(const QString &appId) const
180+{
181+ QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
182+ QSqlQuery query(connection);
183+ query.prepare(m_getStageQuery);
184+ query.bindValue(":appId", appId);
185+ query.exec();
186+ if (!query.isActive() || !query.isSelect()) {
187+ logSqlError(query);
188+ return -1;
189+ }
190+ if (!query.first()) {
191+ return -1;
192+ }
193+ bool converted = false;
194+ QVariant resultStr = query.value(0);
195+ int result = resultStr.toInt(&converted);
196+ if (converted) {
197+ return result;
198+ } else {
199+ qWarning() << "getStage result expected integer, got " << resultStr;
200+ return -1;
201+ }
96202}
97203
98−int WindowStateStorage::getStage(const QString &appId, int defaultValue) const
204+void AsyncQuery::logSqlError(const QSqlQuery query) const
99205{
100− const QString queryString = QStringLiteral("SELECT stage FROM stage WHERE appId = '%1';")
101− .arg(sanitiseString(appId));
206+ qWarning() << "Error executing query" << query.lastQuery()
207+ << "Driver error:" << query.lastError().driverText()
208+ << "Database error:" << query.lastError().databaseText();
209+}
102210
103− QSqlQuery query = getValue(queryString);
211+const QString AsyncQuery::getDbName()
212+{
213+ QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
214+ return connection.databaseName();
215+}
104216
105− if (!query.first()) {
106− return defaultValue;
217+WindowStateStorage::WindowStateStorage(const QString& dbName, QObject *parent):
218+ QObject(parent),
219+ m_thread()
220+{
221+ QString dbFile;
222+ if (dbName == nullptr) {
223+ const QString dbPath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/unity8/");
224+ QDir dir;
225+ dir.mkpath(dbPath);
226+ dbFile = QString(dbPath) + QStringLiteral("windowstatestorage.sqlite");
227+ } else {
228+ dbFile = dbName;
229+ }
230+ m_asyncQuery = new AsyncQuery(dbFile);
231+ m_asyncQuery->moveToThread(&m_thread);
232+ connect(&m_thread, &QThread::finished, m_asyncQuery, &QObject::deleteLater);
233+ m_thread.start();
234+ bool queryInitSuccessful;
235+ QMetaObject::invokeMethod(m_asyncQuery, "initdb",
236+ Qt::BlockingQueuedConnection,
237+ Q_RETURN_ARG(bool, queryInitSuccessful));
238+ if (!queryInitSuccessful) {
239+ qWarning() << "Failed to initialize WindowStateStorage! Windows will not be restored to their previous location.";
240+ m_asyncOk = false;
241+ m_thread.quit();
242+ } else {
243+ connect(this, &WindowStateStorage::saveGeometry, m_asyncQuery, &AsyncQuery::saveGeometry);
244+ connect(this, &WindowStateStorage::saveStage, m_asyncQuery, &AsyncQuery::saveStage);
245+ m_asyncOk = true;
107246 }
108− return query.value("stage").toInt();
109247}
110248
111−QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue) const
249+WindowStateStorage::~WindowStateStorage()
112250{
113− QString queryString = QStringLiteral("SELECT * FROM geometry WHERE windowId = '%1';")
114− .arg(sanitiseString(windowId));
251+ m_thread.quit();
252+ m_thread.wait();
253+}
115254
116− QSqlQuery query = getValue(queryString);
255+void WindowStateStorage::saveState(const QString &windowId, WindowStateStorage::WindowState windowState)
256+{
257+ if (!m_asyncOk) {
258+ return;
259+ }
260+ // This could be a simple connection like all the other save* methods
261+ // on AsyncQuery, but WindowStateStorage::WindowState can't be used
262+ // on it directly.
263+ QMetaObject::invokeMethod(m_asyncQuery, "saveState", Qt::QueuedConnection,
264+ Q_ARG(const QString&, windowId),
265+ Q_ARG(int, (int)windowState)
266+ );
267+}
117268
118− if (!query.first()) {
269+WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue) const
270+{
271+ if (!m_asyncOk) {
119272 return defaultValue;
120273 }
274+ int state;
121275
122− const QRect result(query.value(QStringLiteral("x")).toInt(), query.value(QStringLiteral("y")).toInt(),
123− query.value(QStringLiteral("width")).toInt(), query.value(QStringLiteral("height")).toInt());
276+ QMetaObject::invokeMethod(m_asyncQuery, "getState", Qt::BlockingQueuedConnection,
277+ Q_RETURN_ARG(int, state),
278+ Q_ARG(const QString&, windowId)
279+ );
124280
125− if (result.isValid()) {
126− return result;
281+ if (state == -1) {
282+ return defaultValue;
127283 }
128284
129− return defaultValue;
285+ return (WindowState)state;
130286}
131287
132−void WindowStateStorage::initdb()
288+int WindowStateStorage::getStage(const QString &appId, int defaultValue) const
133289{
134− m_db.open();
135− if (!m_db.open()) {
136− qWarning() << "Error opening state database:" << m_db.lastError().driverText() << m_db.lastError().databaseText();
137− return;
290+ if (!m_asyncOk) {
291+ return defaultValue;
138292 }
293+ int stage;
139294
140− if (!m_db.tables().contains(QStringLiteral("geometry"))) {
141− QSqlQuery query;
142− query.exec(QStringLiteral("CREATE TABLE geometry(windowId TEXT UNIQUE, x INTEGER, y INTEGER, width INTEGER, height INTEGER);"));
143− }
295+ QMetaObject::invokeMethod(m_asyncQuery, "getStage", Qt::BlockingQueuedConnection,
296+ Q_RETURN_ARG(int, stage),
297+ Q_ARG(const QString&, appId)
298+ );
144299
145− if (!m_db.tables().contains(QStringLiteral("state"))) {
146− QSqlQuery query;
147− query.exec(QStringLiteral("CREATE TABLE state(windowId TEXT UNIQUE, state INTEGER);"));
300+ if (stage == -1) {
301+ return defaultValue;
148302 }
149303
150− if (!m_db.tables().contains(QStringLiteral("stage"))) {
151− QSqlQuery query;
152− query.exec(QStringLiteral("CREATE TABLE stage(appId TEXT UNIQUE, stage INTEGER);"));
153− }
304+ return stage;
154305}
155306
156−void WindowStateStorage::saveValue(const QString &queryString)
307+QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue) const
157308{
158− QMutexLocker mutexLocker(&s_mutex);
159− QSqlQuery query;
160− auto ok = query.exec(queryString);
161− if (!ok) {
162− qWarning() << "Error executing query" << queryString
163− << "Driver error:" << query.lastError().driverText()
164− << "Database error:" << query.lastError().databaseText();
309+ if (!m_asyncOk) {
310+ return defaultValue;
165311 }
312+ QRect geometry;
313+ QMetaObject::invokeMethod(m_asyncQuery, "getGeometry", Qt::BlockingQueuedConnection,
314+ Q_RETURN_ARG(QRect, geometry),
315+ Q_ARG(const QString&, windowId)
316+ );
317+ if (geometry.isNull() || !geometry.isValid()) {
318+ return defaultValue;
319+ }
320+ return geometry;
166321}
167322
168−QSqlQuery WindowStateStorage::getValue(const QString &queryString) const
323+const QString WindowStateStorage::getDbName()
169324{
170− QMutexLocker l(&s_mutex);
171− QSqlQuery query;
172−
173− bool ok = query.exec(queryString);
174− if (!ok) {
175− qWarning() << "Error retrieving database query:" << queryString
176− << "Driver error:" << query.lastError().driverText()
177− << "Database error:" << query.lastError().databaseText();
325+ if (!m_asyncOk) {
326+ return QStringLiteral("ERROR");
178327 }
179− return query;
328+ QString dbName;
329+ QMetaObject::invokeMethod(m_asyncQuery, "getDbName", Qt::BlockingQueuedConnection,
330+ Q_RETURN_ARG(QString, dbName)
331+ );
332+ return QString(dbName);
180333}
181334
182335Mir::State WindowStateStorage::toMirState(WindowState state) const
@@ -201,3 +354,5 @@ Mir::State WindowStateStorage::toMirState(WindowState state) const
201354 return Mir::RestoredState;
202355 }
203356}
357+
358+#include "windowstatestorage.moc"