1 /**************************************************************************
2 * Otter Browser: Web browser controlled by the user, not vice-versa.
3 * Copyright (C) 2017 - 2018 Michal Dutkiewicz aka Emdek <michal@emdek.pl>
4 * Copyright (C) 2017 Piotr Wójcik <chocimier@tlen.pl>
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 *
19 **************************************************************************/
20 
21 #include "Migrator.h"
22 #include "ActionsManager.h"
23 #include "Application.h"
24 #include "IniSettings.h"
25 #include "JsonSettings.h"
26 #include "SessionsManager.h"
27 #include "SettingsManager.h"
28 #include "ToolBarsManager.h"
29 #include "../ui/ItemViewWidget.h"
30 
31 #include <QtCore/QDate>
32 #include <QtCore/QDir>
33 #include <QtCore/QJsonArray>
34 #include <QtCore/QJsonObject>
35 #include <QtCore/QSettings>
36 #include <QtCore/QTextStream>
37 #include <QtWidgets/QCheckBox>
38 #include <QtWidgets/QDialog>
39 #include <QtWidgets/QDialogButtonBox>
40 #include <QtWidgets/QLabel>
41 #include <QtWidgets/QVBoxLayout>
42 
43 namespace Otter
44 {
45 
46 class KeyboardAndMouseProfilesIniToJsonMigration final : public Migration
47 {
48 public:
KeyboardAndMouseProfilesIniToJsonMigration()49 	KeyboardAndMouseProfilesIniToJsonMigration() : Migration()
50 	{
51 	}
52 
createBackup() const53 	void createBackup() const override
54 	{
55 		const QString keyboardBackupPath(createBackupPath(QLatin1String("keyboard")));
56 		const QList<QFileInfo> keyboardEntries(QDir(SessionsManager::getWritableDataPath(QLatin1String("keyboard"))).entryInfoList({QLatin1String("*.ini")}));
57 
58 		for (int i = 0; i < keyboardEntries.count(); ++i)
59 		{
60 			QFile::copy(keyboardEntries.at(i).absoluteFilePath(), keyboardBackupPath + keyboardEntries.at(i).fileName());
61 		}
62 
63 		const QString mouseBackupPath(createBackupPath(QLatin1String("mouse")));
64 		const QList<QFileInfo> mouseEntries(QDir(SessionsManager::getWritableDataPath(QLatin1String("mouse"))).entryInfoList({QLatin1String("*.ini")}));
65 
66 		for (int i = 0; i < mouseEntries.count(); ++i)
67 		{
68 			QFile::copy(mouseEntries.at(i).absoluteFilePath(), mouseBackupPath + mouseEntries.at(i).fileName());
69 		}
70 	}
71 
migrate() const72 	void migrate() const override
73 	{
74 		const QList<QFileInfo> keyboardEntries(QDir(SessionsManager::getWritableDataPath(QLatin1String("keyboard"))).entryInfoList({QLatin1String("*.ini")}, QDir::Files));
75 
76 		for (int i = 0; i < keyboardEntries.count(); ++i)
77 		{
78 			KeyboardProfile profile(keyboardEntries.at(i).baseName());
79 			QVector<KeyboardProfile::Action> definitions;
80 			IniSettings settings(keyboardEntries.at(i).absoluteFilePath());
81 			const QStringList comments(settings.getComment().split(QLatin1Char('\n')));
82 
83 			for (int j = 0; j < comments.count(); ++j)
84 			{
85 				const QString key(comments.at(j).section(QLatin1Char(':'), 0, 0).trimmed());
86 				const QString value(comments.at(j).section(QLatin1Char(':'), 1).trimmed());
87 
88 				if (key == QLatin1String("Title"))
89 				{
90 					profile.setTitle(value);
91 				}
92 				else if (key == QLatin1String("Description"))
93 				{
94 					profile.setDescription(value);
95 				}
96 				else if (key == QLatin1String("Author"))
97 				{
98 					profile.setAuthor(value);
99 				}
100 				else if (key == QLatin1String("Version"))
101 				{
102 					profile.setVersion(value);
103 				}
104 			}
105 
106 			const QStringList actions(settings.getGroups());
107 
108 			for (int j = 0; j < actions.count(); ++j)
109 			{
110 				const int action(ActionsManager::getActionIdentifier(actions.at(j)));
111 
112 				if (action < 0)
113 				{
114 					continue;
115 				}
116 
117 				settings.beginGroup(actions.at(j));
118 
119 				const QStringList shortcuts(settings.getValue(QLatin1String("shortcuts")).toString().split(QLatin1Char(' '), QString::SkipEmptyParts));
120 				KeyboardProfile::Action definition;
121 				definition.shortcuts.reserve(shortcuts.count());
122 				definition.action = action;
123 
124 				for (int k = 0; k < shortcuts.count(); ++k)
125 				{
126 					const QKeySequence shortcut(QKeySequence(shortcuts.at(k)));
127 
128 					if (!shortcut.isEmpty())
129 					{
130 						definition.shortcuts.append(shortcut);
131 					}
132 				}
133 
134 				if (!definition.shortcuts.isEmpty())
135 				{
136 					definitions.append(definition);
137 				}
138 
139 				settings.endGroup();
140 			}
141 
142 			profile.setDefinitions({{ActionsManager::GenericContext, definitions}});
143 			profile.save();
144 
145 			QFile::remove(keyboardEntries.at(i).absoluteFilePath());
146 		}
147 
148 		const QList<QFileInfo> mouseEntries(QDir(SessionsManager::getWritableDataPath(QLatin1String("mouse"))).entryInfoList({QLatin1String("*.ini")}, QDir::Files));
149 
150 		for (int i = 0; i < mouseEntries.count(); ++i)
151 		{
152 			IniSettings settings(mouseEntries.at(i).absoluteFilePath());
153 			JsonSettings jsonSettings(SessionsManager::getWritableDataPath(QLatin1String("mouse/") + mouseEntries.at(i).completeBaseName() + QLatin1String(".json")));
154 			jsonSettings.setComment(settings.getComment());
155 
156 			const QStringList contexts(settings.getGroups());
157 			QJsonArray contextsArray;
158 
159 			for (int j = 0; j < contexts.count(); ++j)
160 			{
161 				QJsonObject contextObject{{QLatin1String("context"), contexts.at(j)}};
162 				QJsonArray gesturesArray;
163 
164 				settings.beginGroup(contexts.at(j));
165 
166 				const QStringList gestures(settings.getKeys());
167 
168 				for (int k = 0; k < gestures.count(); ++k)
169 				{
170 					gesturesArray.append(QJsonObject{{QLatin1String("action"), settings.getValue(gestures.at(k)).toString()},{QLatin1String("steps"), QJsonArray::fromStringList(gestures.at(k).split(','))}});
171 				}
172 
173 				contextObject.insert(QLatin1String("gestures"), gesturesArray);
174 
175 				contextsArray.append(contextObject);
176 
177 				settings.endGroup();
178 			}
179 
180 			jsonSettings.setArray(contextsArray);
181 			jsonSettings.save();
182 
183 			QFile::remove(mouseEntries.at(i).absoluteFilePath());
184 		}
185 	}
186 
getName() const187 	QString getName() const override
188 	{
189 		return QLatin1String("keyboardAndMouseProfilesIniToJson");
190 	}
191 
getTitle() const192 	QString getTitle() const override
193 	{
194 		return QT_TRANSLATE_NOOP("migrations", "Keyboard and Mouse Configuration Profiles");
195 	}
196 
needsMigration() const197 	bool needsMigration() const override
198 	{
199 		return (!QDir(SessionsManager::getWritableDataPath(QLatin1String("keyboard"))).entryList({QLatin1String("*.ini")}, QDir::Files).isEmpty() || !QDir(SessionsManager::getWritableDataPath(QLatin1String("mouse"))).entryList({QLatin1String("*.ini")}, QDir::Files).isEmpty());
200 	}
201 };
202 
203 class OptionsRenameMigration final : public Migration
204 {
205 public:
OptionsRenameMigration()206 	OptionsRenameMigration() : Migration()
207 	{
208 	}
209 
createBackup() const210 	void createBackup() const override
211 	{
212 		const QString backupPath(createBackupPath(QLatin1String("other")));
213 		const QList<QFileInfo> entries(QDir(SessionsManager::getWritableDataPath({})).entryInfoList({QLatin1String("otter.conf"), QLatin1String("override.ini")}));
214 
215 		for (int i = 0; i < entries.count(); ++i)
216 		{
217 			QFile::copy(entries.at(i).absoluteFilePath(), backupPath + entries.at(i).fileName());
218 		}
219 	}
220 
migrate() const221 	void migrate() const override
222 	{
223 		QMap<QString, SettingsManager::OptionIdentifier> optionsMap;
224 		optionsMap[QLatin1String("Browser/DelayRestoringOfBackgroundTabs")] = SettingsManager::Sessions_DeferTabsLoadingOption;
225 		optionsMap[QLatin1String("Browser/EnableFullScreen")] = SettingsManager::Permissions_EnableFullScreenOption;
226 		optionsMap[QLatin1String("Browser/EnableGeolocation")] = SettingsManager::Permissions_EnableGeolocationOption;
227 		optionsMap[QLatin1String("Browser/EnableImages")] = SettingsManager::Permissions_EnableImagesOption;
228 		optionsMap[QLatin1String("Browser/EnableJavaScript")] = SettingsManager::Permissions_EnableJavaScriptOption;
229 		optionsMap[QLatin1String("Browser/EnableLocalStorage")] = SettingsManager::Permissions_EnableLocalStorageOption;
230 		optionsMap[QLatin1String("Browser/EnableMediaCaptureAudio")] = SettingsManager::Permissions_EnableMediaCaptureAudioOption;
231 		optionsMap[QLatin1String("Browser/EnableMediaCaptureVideo")] = SettingsManager::Permissions_EnableMediaCaptureVideoOption;
232 		optionsMap[QLatin1String("Browser/EnableMediaPlaybackAudio")] = SettingsManager::Permissions_EnableMediaPlaybackAudioOption;
233 		optionsMap[QLatin1String("Browser/EnableNotifications")] = SettingsManager::Permissions_EnableNotificationsOption;
234 		optionsMap[QLatin1String("Browser/EnableOfflineStorageDatabase")] = SettingsManager::Permissions_EnableOfflineStorageDatabaseOption;
235 		optionsMap[QLatin1String("Browser/EnableOfflineWebApplicationCache")] = SettingsManager::Permissions_EnableOfflineWebApplicationCacheOption;
236 		optionsMap[QLatin1String("Browser/EnablePlugins")] = SettingsManager::Permissions_EnablePluginsOption;
237 		optionsMap[QLatin1String("Browser/EnablePointerLock")] = SettingsManager::Permissions_EnablePointerLockOption;
238 		optionsMap[QLatin1String("Browser/EnableWebgl")] = SettingsManager::Permissions_EnableWebglOption;
239 		optionsMap[QLatin1String("Browser/JavaScriptCanAccessClipboard")] = SettingsManager::Permissions_ScriptsCanAccessClipboardOption;
240 		optionsMap[QLatin1String("Browser/JavaScriptCanChangeWindowGeometry")] = SettingsManager::Permissions_ScriptsCanChangeWindowGeometryOption;
241 		optionsMap[QLatin1String("Browser/JavaScriptCanCloseWindows")] = SettingsManager::Permissions_ScriptsCanCloseWindowsOption;
242 		optionsMap[QLatin1String("Browser/JavaScriptCanDisableContextMenu")] = SettingsManager::Permissions_ScriptsCanReceiveRightClicksOption;
243 		optionsMap[QLatin1String("Browser/JavaScriptCanShowStatusMessages")] = SettingsManager::Permissions_ScriptsCanShowStatusMessagesOption;
244 		optionsMap[QLatin1String("Browser/TabCrashingActionOption")] = SettingsManager::Interface_TabCrashingActionOption;
245 		optionsMap[QLatin1String("Content/PopupsPolicy")] = SettingsManager::Permissions_ScriptsCanOpenWindowsOption;
246 
247 		QMap<QString, SettingsManager::OptionIdentifier>::iterator optionsIterator;
248 		QSettings configuration(SettingsManager::getGlobalPath(), QSettings::IniFormat);
249 		const QStringList configurationKeys(configuration.allKeys());
250 
251 		for (optionsIterator = optionsMap.begin(); optionsIterator != optionsMap.end(); ++optionsIterator)
252 		{
253 			if (configurationKeys.contains(optionsIterator.key()))
254 			{
255 				configuration.setValue(SettingsManager::getOptionName(optionsIterator.value()), configuration.value(optionsIterator.key()).toString());
256 				configuration.remove(optionsIterator.key());
257 			}
258 		}
259 
260 		QSettings overrides(SettingsManager::getOverridePath(), QSettings::IniFormat);
261 		const QStringList overridesGroups(overrides.childGroups());
262 
263 		for (int i = 0; i < overridesGroups.count(); ++i)
264 		{
265 			overrides.beginGroup(overridesGroups.at(i));
266 
267 			const QStringList overridesKeys(overrides.allKeys());
268 
269 			for (optionsIterator = optionsMap.begin(); optionsIterator != optionsMap.end(); ++optionsIterator)
270 			{
271 				if (overridesKeys.contains(optionsIterator.key()))
272 				{
273 					overrides.setValue(SettingsManager::getOptionName(optionsIterator.value()), overrides.value(optionsIterator.key()).toString());
274 					overrides.remove(optionsIterator.key());
275 				}
276 			}
277 
278 			overrides.endGroup();
279 		}
280 
281 		const QStringList sessions(SessionsManager::getSessions());
282 
283 		for (int i = 0; i < sessions.count(); ++i)
284 		{
285 			QFile file(SessionsManager::getSessionPath(sessions.at(i)));
286 
287 			if (file.open(QIODevice::ReadOnly | QIODevice::Text))
288 			{
289 				QString data(file.readAll());
290 
291 				for (optionsIterator = optionsMap.begin(); optionsIterator != optionsMap.end(); ++optionsIterator)
292 				{
293 					data.replace(QLatin1Char('"') + optionsIterator.key() + QLatin1String("\": "), QLatin1Char('"') + SettingsManager::getOptionName(optionsIterator.value()) + QLatin1String("\": "));
294 				}
295 
296 				file.close();
297 
298 				if (file.open(QIODevice::WriteOnly | QIODevice::Text))
299 				{
300 					QTextStream stream(&file);
301 					stream.setCodec("UTF-8");
302 					stream << data;
303 
304 					file.close();
305 				}
306 			}
307 		}
308 
309 		if (configurationKeys.contains(QLatin1String("Sidebar/PanelsOption")) || configurationKeys.contains(QLatin1String("Sidebar/ShowToggleEdgeOption")) || configurationKeys.contains(QLatin1String("Sidebar/VisibleOption")))
310 		{
311 			ToolBarsManager::ToolBarDefinition sidebarDefiniton(ToolBarsManager::getToolBarDefinition(ToolBarsManager::SideBar));
312 			sidebarDefiniton.currentPanel = configuration.value(QLatin1String("Sidebar/CurrentPanelOption")).toString();
313 			sidebarDefiniton.normalVisibility = (configuration.value(QLatin1String("Sidebar/VisibleOption")).toBool() ? ToolBarsManager::AlwaysVisibleToolBar : ToolBarsManager::AlwaysHiddenToolBar);
314 			sidebarDefiniton.panels = configuration.value(QLatin1String("Sidebar/PanelsOption")).toStringList();
315 			sidebarDefiniton.hasToggle = configuration.value(QLatin1String("Sidebar/ShowToggleEdgeOption")).toBool();
316 
317 			ToolBarsManager::setToolBar(sidebarDefiniton);
318 		}
319 	}
320 
getName() const321 	QString getName() const override
322 	{
323 		return QLatin1String("optionsRename");
324 	}
325 
getTitle() const326 	QString getTitle() const override
327 	{
328 		return QT_TRANSLATE_NOOP("migrations", "Options");
329 	}
330 
needsMigration() const331 	bool needsMigration() const override
332 	{
333 		return (QFile::exists(SettingsManager::getGlobalPath()) || QFile::exists(SettingsManager::getOverridePath()));
334 	}
335 };
336 
337 class SearchEnginesStorageMigration final : public Migration
338 {
339 public:
SearchEnginesStorageMigration()340 	SearchEnginesStorageMigration() : Migration()
341 	{
342 	}
343 
migrate() const344 	void migrate() const override
345 	{
346 		QDir().rename(SessionsManager::getWritableDataPath(QLatin1String("searches")), SessionsManager::getWritableDataPath(QLatin1String("searchEngines")));
347 	}
348 
getName() const349 	QString getName() const override
350 	{
351 		return QLatin1String("searchEnginesStorage");
352 	}
353 
getTitle() const354 	QString getTitle() const override
355 	{
356 		return QT_TRANSLATE_NOOP("migrations", "Search Engines");
357 	}
358 
needsBackup() const359 	bool needsBackup() const override
360 	{
361 		return false;
362 	}
363 
needsMigration() const364 	bool needsMigration() const override
365 	{
366 		return QFile::exists(SessionsManager::getWritableDataPath(QLatin1String("searches")));
367 	}
368 };
369 
370 class SessionsIniToJsonMigration final : public Migration
371 {
372 public:
SessionsIniToJsonMigration()373 	SessionsIniToJsonMigration() : Migration()
374 	{
375 	}
376 
createBackup() const377 	void createBackup() const override
378 	{
379 		const QString backupPath(createBackupPath(QLatin1String("sessions")));
380 		const QList<QFileInfo> entries(QDir(SessionsManager::getWritableDataPath(QLatin1String("sessions"))).entryInfoList({QLatin1String("*.ini")}));
381 
382 		for (int i = 0; i < entries.count(); ++i)
383 		{
384 			QFile::copy(entries.at(i).absoluteFilePath(), backupPath + entries.at(i).fileName());
385 		}
386 	}
387 
migrate() const388 	void migrate() const override
389 	{
390 		const QList<QFileInfo> entries(QDir(SessionsManager::getWritableDataPath(QLatin1String("sessions"))).entryInfoList({QLatin1String("*.ini")}, QDir::Files));
391 
392 		for (int i = 0; i < entries.count(); ++i)
393 		{
394 			QSettings sessionData(entries.at(i).absoluteFilePath(), QSettings::IniFormat);
395 			sessionData.setIniCodec("UTF-8");
396 
397 			SessionInformation session;
398 			session.path = entries.at(i).absolutePath() + QDir::separator() + entries.at(i).baseName() + QLatin1String(".json");
399 			session.title = sessionData.value(QLatin1String("Session/title"), {}).toString();
400 			session.index = (sessionData.value(QLatin1String("Session/index"), 1).toInt() - 1);
401 			session.isClean = sessionData.value(QLatin1String("Session/clean"), true).toBool();
402 
403 			const int windowsAmount(sessionData.value(QLatin1String("Session/windows"), 0).toInt());
404 			const int defaultZoom(SettingsManager::getOption(SettingsManager::Content_DefaultZoomOption).toInt());
405 
406 			for (int j = 1; j <= windowsAmount; ++j)
407 			{
408 				const int tabs(sessionData.value(QStringLiteral("%1/Properties/windows").arg(j), 0).toInt());
409 				SessionMainWindow sessionEntry;
410 				sessionEntry.geometry = QByteArray::fromBase64(sessionData.value(QStringLiteral("%1/Properties/geometry").arg(j), {}).toString().toLatin1());
411 				sessionEntry.index = (sessionData.value(QStringLiteral("%1/Properties/index").arg(j), 1).toInt() - 1);
412 
413 				for (int k = 1; k <= tabs; ++k)
414 				{
415 					const QString state(sessionData.value(QStringLiteral("%1/%2/Properties/state").arg(j).arg(k), {}).toString());
416 					const QString searchEngine(sessionData.value(QStringLiteral("%1/%2/Properties/searchEngine").arg(j).arg(k), {}).toString());
417 					const QString userAgent(sessionData.value(QStringLiteral("%1/%2/Properties/userAgent").arg(j).arg(k), {}).toString());
418 					const QStringList geometry(sessionData.value(QStringLiteral("%1/%2/Properties/geometry").arg(j).arg(k), {}).toString().split(QLatin1Char(',')));
419 					const int historyAmount(sessionData.value(QStringLiteral("%1/%2/Properties/history").arg(j).arg(k), 0).toInt());
420 					const int reloadTime(sessionData.value(QStringLiteral("%1/%2/Properties/reloadTime").arg(j).arg(k), -1).toInt());
421 					WindowState windowState;
422 					windowState.geometry = ((geometry.count() == 4) ? QRect(geometry.at(0).simplified().toInt(), geometry.at(1).simplified().toInt(), geometry.at(2).simplified().toInt(), geometry.at(3).simplified().toInt()) : QRect());
423 					windowState.state = ((state == QLatin1String("maximized")) ? Qt::WindowMaximized : ((state == QLatin1String("minimized")) ? Qt::WindowMinimized : Qt::WindowNoState));
424 
425 					SessionWindow sessionWindow;
426 					sessionWindow.state = windowState;
427 					sessionWindow.parentGroup = sessionData.value(QStringLiteral("%1/%2/Properties/group").arg(j).arg(k), 0).toInt();
428 					sessionWindow.historyIndex = (sessionData.value(QStringLiteral("%1/%2/Properties/index").arg(j).arg(k), 1).toInt() - 1);
429 					sessionWindow.isAlwaysOnTop = sessionData.value(QStringLiteral("%1/%2/Properties/alwaysOnTop").arg(j).arg(k), false).toBool();
430 					sessionWindow.isPinned = sessionData.value(QStringLiteral("%1/%2/Properties/pinned").arg(j).arg(k), false).toBool();
431 
432 					if (!searchEngine.isEmpty())
433 					{
434 						sessionWindow.options[SettingsManager::Search_DefaultSearchEngineOption] = searchEngine;
435 					}
436 
437 					if (!userAgent.isEmpty())
438 					{
439 						sessionWindow.options[SettingsManager::Network_UserAgentOption] = userAgent;
440 					}
441 
442 					if (reloadTime >= 0)
443 					{
444 						sessionWindow.options[SettingsManager::Content_PageReloadTimeOption] = reloadTime;
445 					}
446 
447 					for (int l = 1; l <= historyAmount; ++l)
448 					{
449 						const QStringList position(sessionData.value(QStringLiteral("%1/%2/History/%3/position").arg(j).arg(k).arg(l), 1).toStringList());
450 						WindowHistoryEntry historyEntry;
451 						historyEntry.url = sessionData.value(QStringLiteral("%1/%2/History/%3/url").arg(j).arg(k).arg(l), {}).toString();
452 						historyEntry.title = sessionData.value(QStringLiteral("%1/%2/History/%3/title").arg(j).arg(k).arg(l), {}).toString();
453 						historyEntry.position = ((position.count() == 2) ? QPoint(position.at(0).simplified().toInt(), position.at(1).simplified().toInt()) : QPoint(0, 0));
454 						historyEntry.zoom = sessionData.value(QStringLiteral("%1/%2/History/%3/zoom").arg(j).arg(k).arg(l), defaultZoom).toInt();
455 
456 						sessionWindow.history.append(historyEntry);
457 					}
458 
459 					sessionEntry.windows.append(sessionWindow);
460 				}
461 
462 				session.windows.append(sessionEntry);
463 			}
464 
465 			if (SessionsManager::saveSession(session))
466 			{
467 				QFile::remove(entries.at(i).absoluteFilePath());
468 			}
469 		}
470 	}
471 
getName() const472 	QString getName() const override
473 	{
474 		return QLatin1String("sessionsIniToJson");
475 	}
476 
getTitle() const477 	QString getTitle() const override
478 	{
479 		return QT_TRANSLATE_NOOP("migrations", "Sessions");
480 	}
481 
needsMigration() const482 	bool needsMigration() const override
483 	{
484 		return !QDir(SessionsManager::getWritableDataPath(QLatin1String("sessions"))).entryList({QLatin1String("*.ini")}, QDir::Files).isEmpty();
485 	}
486 };
487 
Migration()488 Migration::Migration()
489 {
490 }
491 
~Migration()492 Migration::~Migration()
493 {
494 }
495 
createBackup() const496 void Migration::createBackup() const
497 {
498 }
499 
migrate() const500 void Migration::migrate() const
501 {
502 }
503 
createBackupPath(const QString & sourcePath)504 QString Migration::createBackupPath(const QString &sourcePath)
505 {
506 	QString backupPath(SessionsManager::getWritableDataPath(QLatin1String("backups") + QDir::separator() + (sourcePath.isEmpty() ? QLatin1String("other") : sourcePath)) + QDir::separator());
507 	QString backupName(QDate::currentDate().toString(QLatin1String("yyyyMMdd")));
508 	int i(1);
509 
510 	do
511 	{
512 		const QString path(backupPath + backupName + ((i > 1) ? QStringLiteral("-%1").arg(i) : QString()) + QDir::separator());
513 
514 		if (!QFile::exists(path))
515 		{
516 			backupPath = path;
517 
518 			break;
519 		}
520 
521 		++i;
522 	}
523 	while (true);
524 
525 	QDir().mkpath(backupPath);
526 
527 	return backupPath;
528 }
529 
getName() const530 QString Migration::getName() const
531 {
532 	return {};
533 }
534 
getTitle() const535 QString Migration::getTitle() const
536 {
537 	return {};
538 }
539 
needsBackup() const540 bool Migration::needsBackup() const
541 {
542 	return true;
543 }
544 
needsMigration() const545 bool Migration::needsMigration() const
546 {
547 	return false;
548 }
549 
run()550 bool Migrator::run()
551 {
552 	const QVector<Migration*> availableMigrations({new KeyboardAndMouseProfilesIniToJsonMigration(), new OptionsRenameMigration(), new SearchEnginesStorageMigration(), new SessionsIniToJsonMigration()});
553 	QVector<Migration*> possibleMigrations;
554 	QStringList processedMigrations(SettingsManager::getOption(SettingsManager::Browser_MigrationsOption).toStringList());
555 
556 	for (int i = 0; i < availableMigrations.count(); ++i)
557 	{
558 		if (!processedMigrations.contains(availableMigrations.at(i)->getName()))
559 		{
560 			if (Application::isFirstRun() || !availableMigrations.at(i)->needsMigration())
561 			{
562 				processedMigrations.append(availableMigrations.at(i)->getName());
563 			}
564 			else
565 			{
566 				possibleMigrations.append(availableMigrations.at(i));
567 			}
568 		}
569 	}
570 
571 	if (possibleMigrations.isEmpty())
572 	{
573 		SettingsManager::setOption(SettingsManager::Browser_MigrationsOption, QVariant(processedMigrations));
574 
575 		return true;
576 	}
577 
578 	QDialog dialog;
579 	dialog.setWindowTitle(QCoreApplication::translate("Otter::Migrator", "Settings Migration"));
580 	dialog.setLayout(new QVBoxLayout(&dialog));
581 
582 	QLabel *label(new QLabel(QCoreApplication::translate("Otter::Migrator", "Configuration of the components listed below needs to be updated to new version.\nDo you want to migrate it?"), &dialog));
583 	label->setWordWrap(true);
584 
585 	ItemViewWidget *migrationsViewWidget(new ItemViewWidget(&dialog));
586 	migrationsViewWidget->setModel(new QStandardItemModel(migrationsViewWidget));
587 	migrationsViewWidget->setHeaderHidden(true);
588 	migrationsViewWidget->header()->setStretchLastSection(true);
589 
590 	bool needsBackup(false);
591 
592 	for (int i = 0; i < possibleMigrations.count(); ++i)
593 	{
594 		QStandardItem *item(new QStandardItem(QCoreApplication::translate("migrations", possibleMigrations.at(i)->getTitle().toUtf8().constData())));
595 		item->setFlags(Qt::ItemIsEnabled | Qt::ItemNeverHasChildren | Qt::ItemIsSelectable);
596 
597 		if (possibleMigrations.at(i)->needsBackup())
598 		{
599 			needsBackup = true;
600 		}
601 
602 		migrationsViewWidget->insertRow({item});
603 	}
604 
605 	QCheckBox *createBackupCheckBox(new QCheckBox(QCoreApplication::translate("Otter::Migrator", "Create backup")));
606 	createBackupCheckBox->setChecked(true);
607 	createBackupCheckBox->setEnabled(needsBackup);
608 
609 	QDialogButtonBox *buttonBox(new QDialogButtonBox(&dialog));
610 	buttonBox->addButton(QDialogButtonBox::Yes);
611 	buttonBox->addButton(QDialogButtonBox::No);
612 	buttonBox->addButton(QDialogButtonBox::Abort);
613 
614 	QDialogButtonBox::StandardButton clickedButton(QDialogButtonBox::Yes);
615 
616 	QObject::connect(buttonBox, &QDialogButtonBox::clicked, [&](QAbstractButton *button)
617 	{
618 		clickedButton = buttonBox->standardButton(button);
619 
620 		dialog.close();
621 	});
622 
623 	dialog.layout()->addWidget(label);
624 	dialog.layout()->addWidget(migrationsViewWidget);
625 	dialog.layout()->addWidget(createBackupCheckBox);
626 	dialog.layout()->addWidget(buttonBox);
627 	dialog.exec();
628 
629 	const bool canProceed(clickedButton == QDialogButtonBox::Yes);
630 
631 	if (canProceed || createBackupCheckBox->isChecked())
632 	{
633 		for (int i = 0; i < possibleMigrations.count(); ++i)
634 		{
635 			processedMigrations.append(possibleMigrations.at(i)->getName());
636 
637 			if (createBackupCheckBox->isChecked())
638 			{
639 				possibleMigrations.at(i)->createBackup();
640 			}
641 
642 			if (canProceed)
643 			{
644 				possibleMigrations.at(i)->migrate();
645 			}
646 		}
647 	}
648 
649 	qDeleteAll(availableMigrations);
650 
651 	if (clickedButton == QDialogButtonBox::Abort)
652 	{
653 		return false;
654 	}
655 
656 	SettingsManager::setOption(SettingsManager::Browser_MigrationsOption, QVariant(processedMigrations));
657 
658 	return true;
659 }
660 
661 }
662