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