1 #include "configmigrationwizard.h"
2 #include "ui_configmigrationwizard.h"
3 #include "configmigration.h"
4 #include "configmigrationitem.h"
5 #include "iconmanager.h"
6 #include "uiutils.h"
7 #include "dbtree/dbtree.h"
8 #include "dbtree/dbtreemodel.h"
9 #include "services/config.h"
10 #include "sqlitestudio.h"
11 #include "db/dbsqlite3.h"
12 #include "services/notifymanager.h"
13 #include "services/dbmanager.h"
14 #include "themetuner.h"
15
ConfigMigrationWizard(QWidget * parent,ConfigMigration * cfgMigration)16 ConfigMigrationWizard::ConfigMigrationWizard(QWidget *parent, ConfigMigration* cfgMigration) :
17 QWizard(parent),
18 ui(new Ui::ConfigMigrationWizard),
19 cfgMigration(cfgMigration)
20 {
21 init();
22 }
23
~ConfigMigrationWizard()24 ConfigMigrationWizard::~ConfigMigrationWizard()
25 {
26 clearFunctions();
27 delete ui;
28 }
29
didMigrate()30 bool ConfigMigrationWizard::didMigrate()
31 {
32 return migrated;
33 }
34
accept()35 void ConfigMigrationWizard::accept()
36 {
37 migrate();
38 QWizard::accept();
39 }
40
init()41 void ConfigMigrationWizard::init()
42 {
43 ui->setupUi(this);
44 THEME_TUNER->darkThemeFix(this);
45
46 #ifdef Q_OS_MACX
47 resize(width() + 150, height());
48 setPixmap(QWizard::BackgroundPixmap, addOpacity(ICONMANAGER->getIcon("config_migration")->pixmap(180, 180), 0.4));
49 #endif
50
51 ui->optionsPage->setValidator([=]() -> bool
52 {
53 QString grpName = ui->groupNameEdit->text();
54
55 bool grpOk = true;
56 QString grpErrorMsg;
57 if (ui->dbGroup->isEnabled() && ui->dbGroup->isChecked())
58 {
59 if (grpName.isEmpty())
60 {
61 grpOk = false;
62 grpErrorMsg = tr("Enter a non-empty name.");
63 }
64 else
65 {
66 DbTreeItem* item = DBTREE->getModel()->findItem(DbTreeItem::Type::DIR, grpName);
67 if (item && !item->parentDbTreeItem())
68 {
69 grpOk = false;
70 grpErrorMsg = tr("Top level group named '%1' already exists. Enter a group name that does not exist yet.").arg(grpName);
71 }
72 }
73 }
74
75 setValidState(ui->groupNameEdit, grpOk, grpErrorMsg);
76
77 return grpOk;
78 });
79
80
81 QTreeWidgetItem* treeItem = nullptr;
82 for (ConfigMigrationItem* cfgItem : cfgMigration->getItemsToMigrate())
83 {
84 treeItem = new QTreeWidgetItem({cfgItem->label});
85 treeItem->setData(0, Qt::UserRole, static_cast<int>(cfgItem->type));
86 treeItem->setFlags(treeItem->flags() | Qt::ItemIsUserCheckable);
87 treeItem->setCheckState(0, Qt::Checked);
88 ui->itemsTree->addTopLevelItem(treeItem);
89 }
90
91 connect(ui->dbGroup, SIGNAL(clicked()), ui->optionsPage, SIGNAL(completeChanged()));
92 connect(ui->groupNameEdit, SIGNAL(textChanged(QString)), ui->optionsPage, SIGNAL(completeChanged()));
93 connect(this, SIGNAL(updateOptionsValidation()), ui->optionsPage, SIGNAL(completeChanged()));
94 connect(this, SIGNAL(currentIdChanged(int)), this, SLOT(updateOptions()));
95
96 emit updateOptionsValidation();
97 }
98
migrate()99 void ConfigMigrationWizard::migrate()
100 {
101 Db* oldCfgDb = cfgMigration->getOldCfgDb();
102 if (!oldCfgDb->open())
103 {
104 notifyError(tr("Could not open old configuration file in order to migrate settings from it."));
105 return;
106 }
107
108 QString cfgFilePath = SQLITESTUDIO->getConfig()->getConfigFilePath();
109 Db* newCfgDb = new DbSqlite3("Config migration connection", cfgFilePath, {{DB_PURE_INIT, true}});
110 if (!newCfgDb->open())
111 {
112 notifyError(tr("Could not open current configuration file in order to migrate settings from old configuration file."));
113 delete newCfgDb;
114 return;
115 }
116
117 newCfgDb->begin();
118 bool migrated = migrateSelected(oldCfgDb, newCfgDb);
119 if (migrated && !newCfgDb->commit())
120 {
121 notifyError(tr("Could not commit migrated data into new configuration file: %1").arg(newCfgDb->getErrorText()));
122 newCfgDb->rollback();
123 }
124 else if (!migrated)
125 {
126 newCfgDb->rollback();
127 }
128 else
129 {
130 finalize();
131 }
132 oldCfgDb->close();
133 newCfgDb->close();
134 delete newCfgDb;
135 clearFunctions();
136 }
137
migrateSelected(Db * oldCfgDb,Db * newCfgDb)138 bool ConfigMigrationWizard::migrateSelected(Db* oldCfgDb, Db* newCfgDb)
139 {
140 if (checkedTypes.contains(ConfigMigrationItem::Type::BUG_REPORTS) && !migrateBugReports(oldCfgDb, newCfgDb))
141 return false;
142
143 if (checkedTypes.contains(ConfigMigrationItem::Type::DATABASES) && !migrateDatabases(oldCfgDb, newCfgDb))
144 return false;
145
146 if (checkedTypes.contains(ConfigMigrationItem::Type::FUNCTION_LIST) && !migrateFunction(oldCfgDb, newCfgDb))
147 return false;
148
149 if (checkedTypes.contains(ConfigMigrationItem::Type::SQL_HISTORY) && !migrateSqlHistory(oldCfgDb, newCfgDb))
150 return false;
151
152 return true;
153 }
154
migrateBugReports(Db * oldCfgDb,Db * newCfgDb)155 bool ConfigMigrationWizard::migrateBugReports(Db* oldCfgDb, Db* newCfgDb)
156 {
157 static_qstring(oldBugsQuery, "SELECT created_on, brief, url, type FROM bugs");
158 static_qstring(newBugsInsert, "INSERT INTO reports_history (timestamp, feature_request, title, url) VALUES (?, ?, ?, ?)");
159
160 SqlQueryPtr insertResults;
161 SqlResultsRowPtr row;
162 SqlQueryPtr results = oldCfgDb->exec(oldBugsQuery);
163 if (results->isError())
164 {
165 notifyError(tr("Could not read bug reports history from old configuration file in order to migrate it: %1").arg(results->getErrorText()));
166 return false;
167 }
168
169 bool feature;
170 QString url;
171 while (results->hasNext())
172 {
173 row = results->next();
174 feature = (row->value("type").toString().toUpper() == "FEATURE");
175 url = row->value("url").toString().trimmed();
176 if (url.startsWith("http://") && url.contains("sqlitestudio.one.pl"))
177 url.replace("sqlitestudio.one.pl", "sqlitestudio.pl").replace("report_bug.rvt", "report_bug3.rvt");
178
179 insertResults = newCfgDb->exec(newBugsInsert, {row->value("created_on"), feature, row->value("brief"), url});
180 if (insertResults->isError())
181 {
182 notifyError(tr("Could not insert a bug reports history entry into new configuration file: %1").arg(insertResults->getErrorText()));
183 return false;
184 }
185 }
186
187 return true;
188 }
189
migrateDatabases(Db * oldCfgDb,Db * newCfgDb)190 bool ConfigMigrationWizard::migrateDatabases(Db* oldCfgDb, Db* newCfgDb)
191 {
192 static_qstring(oldDbListQuery, "SELECT name, path FROM dblist");
193 static_qstring(newDbListInsert, "INSERT INTO dblist (name, path) VALUES (?, ?)");
194 static_qstring(groupOrderQuery, "SELECT max([order]) + 1 FROM groups WHERE parent %1");
195 static_qstring(groupInsert, "INSERT INTO groups (name, [order], parent, open, dbname) VALUES (?, ?, ?, ?, ?)");
196
197 SqlQueryPtr groupResults;
198 SqlQueryPtr insertResults;
199 SqlResultsRowPtr row;
200 SqlQueryPtr results = oldCfgDb->exec(oldDbListQuery);
201 if (results->isError())
202 {
203 notifyError(tr("Could not read database list from old configuration file in order to migrate it: %1").arg(results->getErrorText()));
204 return false;
205 }
206
207 // Creating containing group
208 bool putInGroup = ui->dbGroup->isEnabled() && ui->dbGroup->isChecked();
209 qint64 groupId = -1;
210 int order;
211 if (putInGroup)
212 {
213 // Query order
214 groupResults = newCfgDb->exec(groupOrderQuery.arg("IS NULL"));
215 if (groupResults->isError())
216 {
217 notifyError(tr("Could not query for available order for containing group in new configuration file in order to migrate the database list: %1")
218 .arg(groupResults->getErrorText()));
219 return false;
220 }
221
222 order = groupResults->getSingleCell().toInt();
223
224 // Insert group
225 groupResults = newCfgDb->exec(groupInsert, {ui->groupNameEdit->text(), order, QVariant(), 1, QVariant()});
226 if (groupResults->isError())
227 {
228 notifyError(tr("Could not create containing group in new configuration file in order to migrate the database list: %1").arg(groupResults->getErrorText()));
229 return false;
230 }
231 groupId = groupResults->getRegularInsertRowId();
232 }
233
234 // Migrating the list
235 QString name;
236 QString path;
237 while (results->hasNext())
238 {
239 row = results->next();
240 name = row->value("name").toString();
241 path = row->value("path").toString();
242
243 if (DBLIST->getByName(name) || DBLIST->getByPath(path)) // already on the new list
244 continue;
245
246 insertResults = newCfgDb->exec(newDbListInsert, {name, path});
247 if (insertResults->isError())
248 {
249 notifyError(tr("Could not insert a database entry into new configuration file: %1").arg(insertResults->getErrorText()));
250 return false;
251 }
252
253 // Query order
254 if (putInGroup)
255 groupResults = newCfgDb->exec(groupOrderQuery.arg("= ?"), {groupId});
256 else
257 groupResults = newCfgDb->exec(groupOrderQuery.arg("IS NULL"));
258
259 if (groupResults->isError())
260 {
261 notifyError(tr("Could not query for available order for next database in new configuration file in order to migrate the database list: %1")
262 .arg(groupResults->getErrorText()));
263 return false;
264 }
265
266 order = groupResults->getSingleCell().toInt();
267
268 // Insert group
269 groupResults = newCfgDb->exec(groupInsert, {QVariant(), order, putInGroup ? QVariant(groupId) : QVariant(), 0, name});
270 if (groupResults->isError())
271 {
272 notifyError(tr("Could not create group referencing the database in new configuration file: %1").arg(groupResults->getErrorText()));
273 return false;
274 }
275 }
276
277 return true;
278 }
279
migrateFunction(Db * oldCfgDb,Db * newCfgDb)280 bool ConfigMigrationWizard::migrateFunction(Db* oldCfgDb, Db* newCfgDb)
281 {
282 UNUSED(newCfgDb);
283
284 static_qstring(oldFunctionsQuery, "SELECT name, type, code FROM functions");
285
286 SqlResultsRowPtr row;
287 SqlQueryPtr results = oldCfgDb->exec(oldFunctionsQuery);
288 if (results->isError())
289 {
290 notifyError(tr("Could not read function list from old configuration file in order to migrate it: %1").arg(results->getErrorText()));
291 return false;
292 }
293
294 clearFunctions();
295 for (FunctionManager::ScriptFunction* fn : FUNCTIONS->getAllScriptFunctions())
296 fnList << new FunctionManager::ScriptFunction(*fn);
297
298 FunctionManager::ScriptFunction* fn = nullptr;
299 while (results->hasNext())
300 {
301 row = results->next();
302
303 fn = new FunctionManager::ScriptFunction();
304 fn->type = FunctionManager::ScriptFunction::SCALAR;
305 fn->lang = row->value("type").toString();
306 fn->name = row->value("name").toString();
307 fn->code = row->value("code").toString();
308 fnList << fn;
309 }
310
311 return true;
312 }
313
migrateSqlHistory(Db * oldCfgDb,Db * newCfgDb)314 bool ConfigMigrationWizard::migrateSqlHistory(Db* oldCfgDb, Db* newCfgDb)
315 {
316 static_qstring(historyIdQuery, "SELECT CASE WHEN max(id) IS NULL THEN 0 ELSE max(id) + 1 END FROM sqleditor_history");
317 static_qstring(oldHistoryQuery, "SELECT dbname, date, time, rows, sql FROM history");
318 static_qstring(newHistoryInsert, "INSERT INTO sqleditor_history (id, dbname, date, time_spent, rows, sql) VALUES (?, ?, ?, ?, ?, ?)");
319
320 SqlQueryPtr insertResults;
321 SqlResultsRowPtr row;
322 SqlQueryPtr results = oldCfgDb->exec(oldHistoryQuery);
323 if (results->isError())
324 {
325 notifyError(tr("Could not read SQL queries history from old configuration file in order to migrate it: %1").arg(results->getErrorText()));
326 return false;
327 }
328
329 SqlQueryPtr idResults = newCfgDb->exec(historyIdQuery);
330 if (idResults->isError())
331 {
332 notifyError(tr("Could not read next ID for SQL queries history in new configuration file: %1").arg(idResults->getErrorText()));
333 return false;
334 }
335 qint64 nextId = idResults->getSingleCell().toLongLong();
336
337 int timeSpent;
338 int date;
339 while (results->hasNext())
340 {
341 row = results->next();
342 timeSpent = qRound(row->value("time").toDouble() * 1000);
343 date = QDateTime::fromString(row->value("date").toString(), "yyyy-MM-dd HH:mm").toTime_t();
344
345 insertResults = newCfgDb->exec(newHistoryInsert, {nextId++, row->value("dbname"), date, timeSpent, row->value("rows"), row->value("sql")});
346 if (insertResults->isError())
347 {
348 notifyError(tr("Could not insert SQL history entry into new configuration file: %1").arg(insertResults->getErrorText()));
349 return false;
350 }
351 }
352
353 return true;
354 }
355
finalize()356 void ConfigMigrationWizard::finalize()
357 {
358 if (checkedTypes.contains(ConfigMigrationItem::Type::FUNCTION_LIST))
359 {
360 FUNCTIONS->setScriptFunctions(fnList);
361 fnList.clear();
362 }
363
364 if (checkedTypes.contains(ConfigMigrationItem::Type::SQL_HISTORY))
365 CFG->refreshSqlHistory();
366
367 if (checkedTypes.contains(ConfigMigrationItem::Type::DATABASES))
368 {
369 bool ignore = DBTREE->getModel()->getIgnoreDbLoadedSignal();
370 DBTREE->getModel()->setIgnoreDbLoadedSignal(true);
371 DBLIST->scanForNewDatabasesInConfig();
372 DBTREE->getModel()->setIgnoreDbLoadedSignal(ignore);
373 DBTREE->getModel()->loadDbList();
374 }
375
376 migrated = true;
377 }
378
collectCheckedTypes()379 void ConfigMigrationWizard::collectCheckedTypes()
380 {
381 checkedTypes.clear();
382
383 QTreeWidgetItem* item = nullptr;
384 for (int i = 0, total = ui->itemsTree->topLevelItemCount(); i < total; ++i)
385 {
386 item = ui->itemsTree->topLevelItem(i);
387 if (item->checkState(0) != Qt::Checked)
388 continue;
389
390 checkedTypes << static_cast<ConfigMigrationItem::Type>(item->data(0, Qt::UserRole).toInt());
391 }
392 }
393
clearFunctions()394 void ConfigMigrationWizard::clearFunctions()
395 {
396 for (FunctionManager::ScriptFunction* fn : fnList)
397 delete fn;
398
399 fnList.clear();
400 }
401
updateOptions()402 void ConfigMigrationWizard::updateOptions()
403 {
404 if (currentPage() == ui->optionsPage)
405 {
406 collectCheckedTypes();
407 ui->dbGroup->setEnabled(checkedTypes.contains(ConfigMigrationItem::Type::DATABASES));
408 }
409 }
410