Unity 8
windowstatestorage.cpp
1 /*
2  * Copyright 2015-2016 Canonical Ltd.
3  *
4  * 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  * the Free Software Foundation; version 3.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 #include "windowstatestorage.h"
18 
19 #include <QtConcurrent>
20 #include <QDebug>
21 #include <QFutureSynchronizer>
22 #include <QSqlQuery>
23 #include <QSqlError>
24 #include <QSqlResult>
25 #include <QRect>
26 #include <unity/shell/application/ApplicationInfoInterface.h>
27 
28 QMutex WindowStateStorage::s_mutex;
29 
30 inline QString sanitiseString(QString string) {
31  return string.remove(QLatin1Char('\"'))
32  .remove(QLatin1Char('\''))
33  .remove(QLatin1Char('\\'));
34 }
35 
36 WindowStateStorage::WindowStateStorage(QObject *parent):
37  QObject(parent)
38 {
39  const QString dbPath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/unity8/");
40  m_db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"));
41  QDir dir;
42  dir.mkpath(dbPath);
43  m_db.setDatabaseName(dbPath + "windowstatestorage.sqlite");
44  initdb();
45 }
46 
47 WindowStateStorage::~WindowStateStorage()
48 {
49  QFutureSynchronizer<void> futureSync;
50  for (int i = 0; i < m_asyncQueries.count(); ++i) {
51  futureSync.addFuture(m_asyncQueries[i]);
52  }
53  futureSync.waitForFinished();
54  m_db.close();
55 }
56 
57 void WindowStateStorage::saveState(const QString &windowId, WindowStateStorage::WindowState state)
58 {
59  const QString queryString = QStringLiteral("INSERT OR REPLACE INTO state (windowId, state) values ('%1', '%2');")
60  .arg(sanitiseString(windowId))
61  .arg((int)state);
62 
63  saveValue(queryString);
64 }
65 
66 WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue) const
67 {
68  const QString queryString = QStringLiteral("SELECT state FROM state WHERE windowId = '%1';")
69  .arg(sanitiseString(windowId));
70 
71  QSqlQuery query = getValue(queryString);
72 
73  if (!query.first()) {
74  return defaultValue;
75  }
76  return (WindowState)query.value(QStringLiteral("state")).toInt();
77 }
78 
79 void WindowStateStorage::saveGeometry(const QString &windowId, const QRect &rect)
80 {
81  const QString queryString = QStringLiteral("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values ('%1', '%2', '%3', '%4', '%5');")
82  .arg(sanitiseString(windowId))
83  .arg(rect.x())
84  .arg(rect.y())
85  .arg(rect.width())
86  .arg(rect.height());
87 
88  saveValue(queryString);
89 }
90 
91 void WindowStateStorage::saveStage(const QString &appId, int stage)
92 {
93  const QString queryString = QStringLiteral("INSERT OR REPLACE INTO stage (appId, stage) values ('%1', '%2');")
94  .arg(sanitiseString(appId))
95  .arg(stage);
96 
97  saveValue(queryString);
98 }
99 
100 int WindowStateStorage::getStage(const QString &appId, int defaultValue) const
101 {
102  const QString queryString = QStringLiteral("SELECT stage FROM stage WHERE appId = '%1';")
103  .arg(sanitiseString(appId));
104 
105  QSqlQuery query = getValue(queryString);
106 
107  if (!query.first()) {
108  return defaultValue;
109  }
110  return query.value("stage").toInt();
111 }
112 
113 void WindowStateStorage::executeAsyncQuery(const QString &queryString)
114 {
115  QMutexLocker l(&s_mutex);
116  QSqlQuery query;
117 
118  bool ok = query.exec(queryString);
119  if (!ok) {
120  qWarning() << "Error executing query" << queryString
121  << "Driver error:" << query.lastError().driverText()
122  << "Database error:" << query.lastError().databaseText();
123  }
124 }
125 
126 QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue) const
127 {
128  QString queryString = QStringLiteral("SELECT * FROM geometry WHERE windowId = '%1';")
129  .arg(sanitiseString(windowId));
130 
131  QSqlQuery query = getValue(queryString);
132 
133  if (!query.first()) {
134  return defaultValue;
135  }
136 
137  const QRect result(query.value(QStringLiteral("x")).toInt(), query.value(QStringLiteral("y")).toInt(),
138  query.value(QStringLiteral("width")).toInt(), query.value(QStringLiteral("height")).toInt());
139 
140  if (result.isValid()) {
141  return result;
142  }
143 
144  return defaultValue;
145 }
146 
147 void WindowStateStorage::initdb()
148 {
149  m_db.open();
150  if (!m_db.open()) {
151  qWarning() << "Error opening state database:" << m_db.lastError().driverText() << m_db.lastError().databaseText();
152  return;
153  }
154 
155  if (!m_db.tables().contains(QStringLiteral("geometry"))) {
156  QSqlQuery query;
157  query.exec(QStringLiteral("CREATE TABLE geometry(windowId TEXT UNIQUE, x INTEGER, y INTEGER, width INTEGER, height INTEGER);"));
158  }
159 
160  if (!m_db.tables().contains(QStringLiteral("state"))) {
161  QSqlQuery query;
162  query.exec(QStringLiteral("CREATE TABLE state(windowId TEXT UNIQUE, state INTEGER);"));
163  }
164 
165  if (!m_db.tables().contains(QStringLiteral("stage"))) {
166  QSqlQuery query;
167  query.exec(QStringLiteral("CREATE TABLE stage(appId TEXT UNIQUE, stage INTEGER);"));
168  }
169 }
170 
171 void WindowStateStorage::saveValue(const QString &queryString)
172 {
173  QMutexLocker mutexLocker(&s_mutex);
174 
175  QFuture<void> future = QtConcurrent::run(&m_threadPool, executeAsyncQuery, queryString);
176  m_asyncQueries.append(future);
177 
178  QFutureWatcher<void> *futureWatcher = new QFutureWatcher<void>();
179  futureWatcher->setFuture(future);
180  connect(futureWatcher, &QFutureWatcher<void>::finished,
181  this,
182  [=](){ m_asyncQueries.removeAll(futureWatcher->future());
183  futureWatcher->deleteLater(); });
184 }
185 
186 QSqlQuery WindowStateStorage::getValue(const QString &queryString) const
187 {
188  QMutexLocker l(&s_mutex);
189  QSqlQuery query;
190 
191  bool ok = query.exec(queryString);
192  if (!ok) {
193  qWarning() << "Error retrieving database query:" << queryString
194  << "Driver error:" << query.lastError().driverText()
195  << "Database error:" << query.lastError().databaseText();
196  }
197  return query;
198 }
199 
200 Mir::State WindowStateStorage::toMirState(WindowState state) const
201 {
202  // assumes a single state (not an OR of several)
203  switch (state) {
204  case WindowStateMaximized: return Mir::MaximizedState;
205  case WindowStateMinimized: return Mir::MinimizedState;
206  case WindowStateFullscreen: return Mir::FullscreenState;
207  case WindowStateMaximizedLeft: return Mir::MaximizedLeftState;
208  case WindowStateMaximizedRight: return Mir::MaximizedRightState;
209  case WindowStateMaximizedHorizontally: return Mir::HorizMaximizedState;
210  case WindowStateMaximizedVertically: return Mir::VertMaximizedState;
211  case WindowStateMaximizedTopLeft: return Mir::MaximizedTopLeftState;
212  case WindowStateMaximizedTopRight: return Mir::MaximizedTopRightState;
213  case WindowStateMaximizedBottomLeft: return Mir::MaximizedBottomLeftState;
214  case WindowStateMaximizedBottomRight: return Mir::MaximizedBottomRightState;
215 
216  case WindowStateNormal:
217  case WindowStateRestored:
218  default:
219  return Mir::RestoredState;
220  }
221 }