@@ -1,5 +1,6 @@
2 * Copyright 2015-2016 Canonical Ltd.
2 * Copyright 2015-2016 Canonical Ltd.
3+ * Copyright 2021 UBports Foundation
4 * This program is free software; you can redistribute it and/or modify
5 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * it under the terms of the GNU Lesser General Public License as published by
@@ -16,167 +17,319 @@
17#include "windowstatestorage.h"
18#include "windowstatestorage.h"
19−#include <QtConcurrent>
20#include <QDebug>
20#include <QDebug>
22+#include <QMetaObject>
21#include <QSqlQuery>
24#include <QSqlQuery>
22#include <QSqlError>
25#include <QSqlError>
23#include <QSqlResult>
26#include <QSqlResult>
27+#include <QStandardPaths>
24#include <QRect>
28#include <QRect>
25#include <unity/shell/application/ApplicationInfoInterface.h>
29#include <unity/shell/application/ApplicationInfoInterface.h>
27−QMutex WindowStateStorage::s_mutex;
31+AsyncQuery::AsyncQuery(const QString& dbName):
29−inline QString sanitiseString(QString string) {
36+AsyncQuery::~AsyncQuery()
30− return string.remove(QLatin1Char('\"'))
37+{
31− .remove(QLatin1Char('\''))
38+ QSqlDatabase::removeDatabase(m_connectionName);
32− .remove(QLatin1Char('\\'));
35−WindowStateStorage::WindowStateStorage(const QString& dbName, QObject *parent):
41+bool AsyncQuery::initdb()
38− const QString dbPath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/unity8/");
39− m_db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"));
43+ QSqlDatabase connection = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), m_connectionName);
40− QDir dir;
44+ connection.setDatabaseName(m_dbName);
41− dir.mkpath(dbPath);
45+ if (!connection.open()) {
42− if (dbName != nullptr) {
46+ qWarning() << "Error opening state database " << m_dbName << connection.lastError().driverText() << connection.lastError().databaseText();
43− m_db.setDatabaseName(dbName);
47+ return false;
45− m_db.setDatabaseName(dbPath + "windowstatestorage.sqlite");
49+ QSqlQuery query(connection);
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)) {
59+ if (!connection.tables().contains(QStringLiteral("state"))) {
60+ QString stateQuery = QStringLiteral("CREATE TABLE state(windowId TEXT UNIQUE, state INTEGER);");
61+ if (!query.exec(stateQuery)) {
67+ if (!connection.tables().contains(QStringLiteral("stage"))) {
68+ QString stageQuery = QStringLiteral("CREATE TABLE stage(appId TEXT UNIQUE, stage INTEGER);");
69+ if (!query.exec(stageQuery)) {
50−WindowStateStorage::~WindowStateStorage()
77+void AsyncQuery::saveState(const QString &windowId, int state)
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);
55−void WindowStateStorage::saveState(const QString &windowId, WindowStateStorage::WindowState state)
89+int AsyncQuery::getState(const QString &windowId) const
57− const QString queryString = QStringLiteral("INSERT OR REPLACE INTO state (windowId, state) values ('%1', '%2');")
91+ QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
58− .arg(sanitiseString(windowId))
92+ QSqlQuery query(connection);
59− .arg((int)state);
93+ query.prepare(m_getStateQuery);
60−
94+ query.bindValue(":windowId", windowId);
61− saveValue(queryString);
95+ query.exec();
96+ if (!query.isActive() || !query.isSelect()) {
100+ if (!query.first()) {
103+ bool converted = false;
104+ QVariant resultStr = query.value(0);
105+ int result = resultStr.toInt(&converted);
109+ qWarning() << "getState result expected integer, got " << resultStr;
64−WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue) const
114+void AsyncQuery::saveGeometry(const QString &windowId, const QRect &rect)
66− const QString queryString = QStringLiteral("SELECT state FROM state WHERE windowId = '%1';")
116+ QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
67− .arg(sanitiseString(windowId));
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()) {
129+QRect AsyncQuery::getGeometry(const QString &windowId) const
131+ QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
69− QSqlQuery query = getValue(queryString);
132+ QSqlQuery query(connection);
133+ query.prepare(m_getGeometryQuery);
134+ query.bindValue(":windowId", windowId);
136+ if (!query.isActive() || !query.isSelect()) {
71 if (!query.first()) {
141 if (!query.first()) {
72− return defaultValue;
142+ return QRect();
74− return (WindowState)query.value(QStringLiteral("state")).toInt();
77−void WindowStateStorage::saveGeometry(const QString &windowId, const QRect &rect)
145+ bool xConverted, yConverted, widthConverted, heightConverted = false;
78−{
146+ int x, y, width, height;
79− const QString queryString = QStringLiteral("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values ('%1', '%2', '%3', '%4', '%5');")
147+ QVariant xResultStr = query.value(QStringLiteral("x"));
80− .arg(sanitiseString(windowId))
148+ QVariant yResultStr = query.value(QStringLiteral("y"));
81− .arg(rect.x())
149+ QVariant widthResultStr = query.value(QStringLiteral("width"));
82− .arg(rect.y())
150+ QVariant heightResultStr = query.value(QStringLiteral("height"));
83− .arg(rect.width())
151+ x = xResultStr.toInt(&xConverted);
84− .arg(rect.height());
152+ y = yResultStr.toInt(&yConverted);
153+ width = widthResultStr.toInt(&widthConverted);
154+ height = heightResultStr.toInt(&heightConverted);
156+ if (xConverted && yConverted && widthConverted && heightConverted) {
157+ return QRect(x, y, width, height);
159+ qWarning() << "getGeometry result expected integers, got x:"
160+ << xResultStr << "y:" << yResultStr << "width" << widthResultStr
161+ << "height:" << heightResultStr;
86− saveValue(queryString);
89−void WindowStateStorage::saveStage(const QString &appId, int stage)
167+void AsyncQuery::saveStage(const QString &appId, int stage)
91− const QString queryString = QStringLiteral("INSERT OR REPLACE INTO stage (appId, stage) values ('%1', '%2');")
169+ QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
92− .arg(sanitiseString(appId))
170+ QSqlQuery query(connection);
93− .arg(stage);
171+ query.prepare(m_saveStageQuery);
172+ query.bindValue(":appId", appId);
173+ query.bindValue(":stage", stage);
174+ if (!query.exec()) {
95− saveValue(queryString);
179+int AsyncQuery::getStage(const QString &appId) const
181+ QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
182+ QSqlQuery query(connection);
183+ query.prepare(m_getStageQuery);
184+ query.bindValue(":appId", appId);
186+ if (!query.isActive() || !query.isSelect()) {
190+ if (!query.first()) {
193+ bool converted = false;
194+ QVariant resultStr = query.value(0);
195+ int result = resultStr.toInt(&converted);
199+ qWarning() << "getStage result expected integer, got " << resultStr;
98−int WindowStateStorage::getStage(const QString &appId, int defaultValue) const
204+void AsyncQuery::logSqlError(const QSqlQuery query) const
100− const QString queryString = QStringLiteral("SELECT stage FROM stage WHERE appId = '%1';")
206+ qWarning() << "Error executing query" << query.lastQuery()
101− .arg(sanitiseString(appId));
207+ << "Driver error:" << query.lastError().driverText()
208+ << "Database error:" << query.lastError().databaseText();
103− QSqlQuery query = getValue(queryString);
211+const QString AsyncQuery::getDbName()
213+ QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
214+ return connection.databaseName();
105− if (!query.first()) {
217+WindowStateStorage::WindowStateStorage(const QString& dbName, QObject *parent):
106− return defaultValue;
218+ QObject(parent),
222+ if (dbName == nullptr) {
223+ const QString dbPath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/unity8/");
226+ dbFile = QString(dbPath) + QStringLiteral("windowstatestorage.sqlite");
230+ m_asyncQuery = new AsyncQuery(dbFile);
231+ m_asyncQuery->moveToThread(&m_thread);
232+ connect(&m_thread, &QThread::finished, m_asyncQuery, &QObject::deleteLater);
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.";
243+ connect(this, &WindowStateStorage::saveGeometry, m_asyncQuery, &AsyncQuery::saveGeometry);
244+ connect(this, &WindowStateStorage::saveStage, m_asyncQuery, &AsyncQuery::saveStage);
108− return query.value("stage").toInt();
111−QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue) const
249+WindowStateStorage::~WindowStateStorage()
113− QString queryString = QStringLiteral("SELECT * FROM geometry WHERE windowId = '%1';")
251+ m_thread.quit();
114− .arg(sanitiseString(windowId));
252+ m_thread.wait();
116− QSqlQuery query = getValue(queryString);
255+void WindowStateStorage::saveState(const QString &windowId, WindowStateStorage::WindowState windowState)
260+ // This could be a simple connection like all the other save* methods
261+ // on AsyncQuery, but WindowStateStorage::WindowState can't be used
263+ QMetaObject::invokeMethod(m_asyncQuery, "saveState", Qt::QueuedConnection,
264+ Q_ARG(const QString&, windowId),
265+ Q_ARG(int, (int)windowState)
118− if (!query.first()) {
269+WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue) const
119 return defaultValue;
272 return defaultValue;
122− const QRect result(query.value(QStringLiteral("x")).toInt(), query.value(QStringLiteral("y")).toInt(),
276+ QMetaObject::invokeMethod(m_asyncQuery, "getState", Qt::BlockingQueuedConnection,
123− query.value(QStringLiteral("width")).toInt(), query.value(QStringLiteral("height")).toInt());
277+ Q_RETURN_ARG(int, state),
278+ Q_ARG(const QString&, windowId)
125− if (result.isValid()) {
281+ if (state == -1) {
126− return result;
282+ return defaultValue;
129− return defaultValue;
285+ return (WindowState)state;
132−void WindowStateStorage::initdb()
288+int WindowStateStorage::getStage(const QString &appId, int defaultValue) const
134− m_db.open();
290+ if (!m_asyncOk) {
136− qWarning() << "Error opening state database:" << m_db.lastError().driverText() << m_db.lastError().databaseText();
137− return;
291+ return defaultValue;
140− if (!m_db.tables().contains(QStringLiteral("geometry"))) {
295+ QMetaObject::invokeMethod(m_asyncQuery, "getStage", Qt::BlockingQueuedConnection,
141− QSqlQuery query;
296+ Q_RETURN_ARG(int, stage),
142− query.exec(QStringLiteral("CREATE TABLE geometry(windowId TEXT UNIQUE, x INTEGER, y INTEGER, width INTEGER, height INTEGER);"));
297+ Q_ARG(const QString&, appId)
145− if (!m_db.tables().contains(QStringLiteral("state"))) {
300+ if (stage == -1) {
146− QSqlQuery query;
301+ return defaultValue;
147− query.exec(QStringLiteral("CREATE TABLE state(windowId TEXT UNIQUE, state INTEGER);"));
150− if (!m_db.tables().contains(QStringLiteral("stage"))) {
304+ return stage;
152− query.exec(QStringLiteral("CREATE TABLE stage(appId TEXT UNIQUE, stage INTEGER);"));
156−void WindowStateStorage::saveValue(const QString &queryString)
307+QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue) const
158− QMutexLocker mutexLocker(&s_mutex);
309+ if (!m_asyncOk) {
159− QSqlQuery query;
310+ return defaultValue;
160− auto ok = query.exec(queryString);
162− qWarning() << "Error executing query" << queryString
163− << "Driver error:" << query.lastError().driverText()
164− << "Database error:" << query.lastError().databaseText();
313+ QMetaObject::invokeMethod(m_asyncQuery, "getGeometry", Qt::BlockingQueuedConnection,
314+ Q_RETURN_ARG(QRect, geometry),
315+ Q_ARG(const QString&, windowId)
317+ if (geometry.isNull() || !geometry.isValid()) {
318+ return defaultValue;
168−QSqlQuery WindowStateStorage::getValue(const QString &queryString) const
323+const QString WindowStateStorage::getDbName()
170− QMutexLocker l(&s_mutex);
325+ if (!m_asyncOk) {
171− QSqlQuery query;
326+ return QStringLiteral("ERROR");
173− bool ok = query.exec(queryString);
175− qWarning() << "Error retrieving database query:" << queryString
176− << "Driver error:" << query.lastError().driverText()
177− << "Database error:" << query.lastError().databaseText();
329+ QMetaObject::invokeMethod(m_asyncQuery, "getDbName", Qt::BlockingQueuedConnection,
330+ Q_RETURN_ARG(QString, dbName)
179− return query;
332+ return QString(dbName);
182Mir::State WindowStateStorage::toMirState(WindowState state) const
335Mir::State WindowStateStorage::toMirState(WindowState state) const
@@ -201,3 +354,5 @@ Mir::State WindowStateStorage::toMirState(WindowState state) const
201 return Mir::RestoredState;
354 return Mir::RestoredState;
358+#include "windowstatestorage.moc"