Skip to content

Commit 90857b2

Browse files
authoredOct 27, 2017
[FEATURE] Settings migration framework (#5080)
Only run for default profile and only if not run before. Moves settings and symbols from QGIS 2.x to QGIS 3 default profile * --version-migration flag to force migration
1 parent 40955b2 commit 90857b2

File tree

7 files changed

+407
-2
lines changed

7 files changed

+407
-2
lines changed
 

‎resources/2to3migration.txt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# version=1
2+
# If you update this file make sure you bump the above version number it must be higher then the last run.
3+
#oldkey;newkey
4+
5+
# Connections
6+
MSSQL/connections/*;*
7+
Qgis/connections-xyz/*;qgis/connections-xyz/*
8+
Qgis/connections-wms/*;qgis/connections-wms/*
9+
Qgis/connections-wfs/*;qgis/connections-wfs/*
10+
11+
# random stuff
12+
browser/favourites;*
13+
svg/searchPathsForSVG;*
14+
Qgis/compileExpressions;*
15+
16+
# variables
17+
variables/names;*
18+
variables/values;*
19+

‎resources/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
INSTALL(FILES srs.db qgis.db symbology-style.xml spatialite.db customization.xml
1+
INSTALL(FILES srs.db qgis.db symbology-style.xml spatialite.db customization.xml 2to3migration.txt
22
DESTINATION ${QGIS_DATA_DIR}/resources)
33
INSTALL(FILES qgis_global_settings.ini
44
DESTINATION ${QGIS_DATA_DIR})

‎src/app/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ SET(QGIS_APP_SRCS
5252
qgsmapcanvasdockwidget.cpp
5353
qgsmaplayerstyleguiutils.cpp
5454
qgsmapsavedialog.cpp
55+
qgsversionmigration.cpp
5556
qgsrulebasedlabelingwidget.cpp
5657
qgssavestyletodbdialog.cpp
5758
qgssnappinglayertreemodel.cpp

‎src/app/main.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ typedef SInt32 SRefCon;
9999
#include "qgis_app.h"
100100
#include "qgscrashhandler.h"
101101
#include "qgsziputils.h"
102+
#include "qgsversionmigration.h"
102103

103104
#include "qgsuserprofilemanager.h"
104105
#include "qgsuserprofile.h"
@@ -139,6 +140,7 @@ void usage( const QString &appName )
139140
<< QStringLiteral( "\t[--dxf-preset maptheme]\tmap theme to use for dxf output\n" )
140141
<< QStringLiteral( "\t[--profile name]\tload a named profile from the users profiles folder.\n" )
141142
<< QStringLiteral( "\t[--profiles-path path]\tpath to store user profile folders. Will create profiles inside a {path}\\profiles folder \n" )
143+
<< QStringLiteral( "\t[--version-migration]\tforce the settings migration from older version if found\n" )
142144
<< QStringLiteral( "\t[--help]\t\tthis text\n" )
143145
<< QStringLiteral( "\t[--]\t\ttreat all following arguments as FILEs\n\n" )
144146
<< QStringLiteral( " FILE:\n" )
@@ -501,6 +503,7 @@ int main( int argc, char *argv[] )
501503
int mySnapshotHeight = 600;
502504

503505
bool myHideSplash = false;
506+
bool mySettingsMigrationForce = false;
504507
bool mySkipVersionCheck = false;
505508
#if defined(ANDROID)
506509
QgsDebugMsg( QString( "Android: Splash hidden" ) );
@@ -570,6 +573,10 @@ int main( int argc, char *argv[] )
570573
{
571574
myHideSplash = true;
572575
}
576+
else if ( arg == QLatin1String( "--version-migration" ) )
577+
{
578+
mySettingsMigrationForce = true;
579+
}
573580
else if ( arg == QLatin1String( "--noversioncheck" ) || arg == QLatin1String( "-V" ) )
574581
{
575582
mySkipVersionCheck = true;
@@ -849,6 +856,18 @@ int main( int argc, char *argv[] )
849856
}
850857
}
851858

859+
// Settings migration is only supported on the default profile for now.
860+
if ( profileName == "default" )
861+
{
862+
QgsVersionMigration *migration = QgsVersionMigration::canMigrate( 20000, Qgis::QGIS_VERSION_INT );
863+
if ( migration && ( mySettingsMigrationForce || migration->requiresMigration() ) )
864+
{
865+
QgsDebugMsg( "RUNNING MIGRATION" );
866+
migration->runMigration();
867+
delete migration;
868+
}
869+
}
870+
852871
#ifdef Q_OS_MAC
853872
// Set hidpi icons; use SVG icons, as PNGs will be relatively too small
854873
QCoreApplication::setAttribute( Qt::AA_UseHighDpiPixmaps );

‎src/app/qgsversionmigration.cpp

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
/***************************************************************************
2+
qgsversionmigration.cpp - QgsVersionMigration
3+
4+
---------------------
5+
begin : 30.7.2017
6+
copyright : (C) 2017 by nathan
7+
email : woodrow.nathan at gmail dot com
8+
***************************************************************************
9+
* *
10+
* This program is free software; you can redistribute it and/or modify *
11+
* it under the terms of the GNU General Public License as published by *
12+
* the Free Software Foundation; either version 2 of the License, or *
13+
* (at your option) any later version. *
14+
* *
15+
***************************************************************************/
16+
#include "qgsversionmigration.h"
17+
#include "qgssettings.h"
18+
#include "qgslogger.h"
19+
#include "qsettings.h"
20+
#include "qgsmessagelog.h"
21+
#include "qgsapplication.h"
22+
#include "qgssymbol.h"
23+
#include "qgsstyle.h"
24+
#include "qgssymbollayerutils.h"
25+
#include "qgsreadwritecontext.h"
26+
27+
#include <QFile>
28+
#include <QTextStream>
29+
#include <QDir>
30+
#include <QSqlDatabase>
31+
#include <QSqlQuery>
32+
#include <QSqlError>
33+
#include <QDomDocument>
34+
35+
QgsVersionMigration::QgsVersionMigration()
36+
{
37+
38+
}
39+
40+
QgsVersionMigration::~QgsVersionMigration()
41+
{
42+
43+
}
44+
45+
QgsVersionMigration *QgsVersionMigration::canMigrate( int fromVersion, int toVersion )
46+
{
47+
if ( fromVersion == 20000 && toVersion >= 29900 )
48+
{
49+
return new Qgs2To3Migration();
50+
}
51+
return nullptr;
52+
}
53+
54+
QgsError Qgs2To3Migration::runMigration()
55+
{
56+
QgsError error;
57+
QgsError settingsErrors = migrateSettings();
58+
if ( !settingsErrors.isEmpty() )
59+
{
60+
// TODO Merge error messages
61+
}
62+
QgsError stylesError = migrateStyles();
63+
return error;
64+
}
65+
66+
bool Qgs2To3Migration::requiresMigration()
67+
{
68+
QgsSettings settings;
69+
bool alreadyMigrated = settings.value( QStringLiteral( "migration/settings" ), false ).toBool();
70+
int settingsMigrationVersion = settings.value( QStringLiteral( "migration/fileVersion" ), 0 ).toInt();
71+
QFile migrationFile( migrationFilePath() );
72+
if ( migrationFile.open( QIODevice::ReadOnly | QIODevice::Text ) )
73+
{
74+
QTextStream in( &migrationFile );
75+
QString line = in.readLine();
76+
if ( line.startsWith( "#" ) && line.contains( QStringLiteral( "version=" ) ) )
77+
{
78+
QStringList parts = line.split( '=' );
79+
mMigrationFileVersion = parts.at( 1 ).toInt();
80+
QgsDebugMsg( QString( "File version is=%1" ).arg( mMigrationFileVersion ) );
81+
}
82+
migrationFile.close();
83+
}
84+
else
85+
{
86+
QString msg = QString( "Can not open %1" ).arg( migrationFile.fileName() );
87+
QgsDebugMsg( msg );
88+
mMigrationFileVersion = settingsMigrationVersion;
89+
}
90+
91+
return ( !alreadyMigrated || settingsMigrationVersion != mMigrationFileVersion );
92+
}
93+
94+
QgsError Qgs2To3Migration::migrateStyles()
95+
{
96+
QgsError error;
97+
QString oldHome = QStringLiteral( "%1/.qgis2" ).arg( QDir::homePath() );
98+
QString oldStyleFile = QStringLiteral( "%1/symbology-ng-style.db" ).arg( oldHome );
99+
QgsDebugMsg( QString( "OLD STYLE FILE %1" ).arg( oldStyleFile ) );
100+
QSqlDatabase db = QSqlDatabase::addDatabase( "QSQLITE", "migration" );
101+
db.setDatabaseName( oldStyleFile );
102+
if ( !db.open() )
103+
{
104+
error.append( db.lastError().text() );
105+
QgsDebugMsg( db.lastError().text() );
106+
return error;
107+
}
108+
109+
QSqlQuery query( db );
110+
QSqlQuery tagQuery( "SELECT name FROM tag"
111+
"JOIN tagmap ON tagmap.tag_id = tag.id"
112+
"WHERE tagmap.symbol_id = :symbol_id", db );
113+
114+
QgsStyle *style = QgsStyle::defaultStyle();
115+
if ( query.exec( "SELECT id, name, xml FROM symbol" ) )
116+
{
117+
while ( query.next() )
118+
{
119+
QString symbol_id = query.value( 0 ).toString();
120+
QString name = query.value( 1 ).toString();
121+
QString xml = query.value( 2 ).toString();
122+
QDomDocument doc;
123+
if ( !doc.setContent( xml ) )
124+
{
125+
QgsDebugMsg( "Cannot open symbol " + name );
126+
continue;
127+
}
128+
129+
tagQuery.bindValue( ":symbol_id", symbol_id );
130+
131+
QStringList tags;
132+
if ( tagQuery.exec() )
133+
{
134+
while ( query.next() )
135+
{
136+
QString tagname = query.value( 0 ).toString();
137+
tags << tagname;
138+
}
139+
}
140+
141+
QDomElement symElement = doc.documentElement();
142+
QgsDebugMsg( QString( "MIGRATION: Importing %1" ).arg( name ) );
143+
QgsSymbol *symbol = QgsSymbolLayerUtils::loadSymbol( symElement, QgsReadWriteContext() );
144+
tags << "QGIS 2";
145+
if ( style->symbolId( name ) == 0 )
146+
{
147+
style->saveSymbol( name, symbol, false, tags );
148+
}
149+
}
150+
}
151+
152+
QgsDebugMsg( oldStyleFile );
153+
return error;
154+
}
155+
156+
QgsError Qgs2To3Migration::migrateSettings()
157+
{
158+
QgsError error;
159+
160+
QgsSettings newSettings;
161+
162+
// The platform default location for the settings from 2.x
163+
mOldSettings = new QSettings( "QGIS", "QGIS2" );
164+
165+
QFile inputFile( migrationFilePath() );
166+
std::map<QString, QgsSettings::Section> sections;
167+
sections["none"] = QgsSettings::NoSection;
168+
sections["core"] = QgsSettings::Core;
169+
sections["gui"] = QgsSettings::Gui;
170+
sections["server"] = QgsSettings::Server;
171+
sections["plugins"] = QgsSettings::Plugins;
172+
sections["auth"] = QgsSettings::Auth;
173+
sections["app"] = QgsSettings::App;
174+
sections["providers"] = QgsSettings::Providers;
175+
sections["misc"] = QgsSettings::Misc;
176+
177+
QList<QPair<QString, QString>> keys;
178+
179+
if ( inputFile.open( QIODevice::ReadOnly | QIODevice::Text ) )
180+
{
181+
QTextStream in( &inputFile );
182+
while ( !in.atEnd() )
183+
{
184+
QString line = in.readLine();
185+
186+
if ( line.startsWith( "#" ) )
187+
continue;
188+
189+
if ( line.isEmpty() )
190+
continue;
191+
192+
QStringList parts = line.split( ";" );
193+
194+
Q_ASSERT_X( parts.count() == 2, "QgsVersionMigration::migrateSettings()", "Can't split line in 2 parts." );
195+
196+
QString oldKey = parts.at( 0 );
197+
QString newKey = parts.at( 1 );
198+
199+
if ( oldKey.endsWith( "/*" ) )
200+
{
201+
oldKey = oldKey.replace( "/*", "" );
202+
QList<QPair<QString, QString>> keyList = walk( oldKey, newKey );
203+
keys.append( keyList );
204+
}
205+
else
206+
{
207+
QPair<QString, QString> key = transformKey( oldKey, newKey );
208+
keys.append( key );
209+
}
210+
211+
}
212+
inputFile.close();
213+
newSettings.setValue( QStringLiteral( "migration/settings" ), true );
214+
// Set the dev gen so we can force a migration.
215+
newSettings.setValue( QStringLiteral( "migration/fileVersion" ), mMigrationFileVersion );
216+
}
217+
else
218+
{
219+
QString msg = QString( "Can not open %1" ).arg( inputFile.fileName() );
220+
QgsDebugMsg( msg );
221+
error.append( msg );
222+
}
223+
224+
if ( keys.count() > 0 )
225+
{
226+
QgsDebugMsg( "MIGRATION: Translating settings keys" );
227+
QList<QPair<QString, QString>>::iterator i;
228+
for ( i = keys.begin(); i != keys.end(); ++i )
229+
{
230+
QPair<QString, QString> pair = *i;
231+
232+
QString oldKey = pair.first;
233+
QString newKey = pair.second;
234+
235+
if ( oldKey.contains( oldKey ) )
236+
{
237+
QgsDebugMsg( QString( " -> %1 -> %2" ).arg( oldKey, newKey ) );
238+
newSettings.setValue( newKey, mOldSettings->value( oldKey ) );
239+
}
240+
}
241+
}
242+
return error;
243+
}
244+
245+
QList<QPair<QString, QString> > Qgs2To3Migration::walk( QString group, QString newkey )
246+
{
247+
mOldSettings->beginGroup( group );
248+
QList<QPair<QString, QString> > foundKeys;
249+
Q_FOREACH ( const QString &group, mOldSettings->childGroups() )
250+
{
251+
QList<QPair<QString, QString> > data = walk( group, newkey );
252+
foundKeys.append( data );
253+
}
254+
255+
Q_FOREACH ( const QString &key, mOldSettings->childKeys() )
256+
{
257+
QString fullKey = mOldSettings->group() + "/" + key;
258+
foundKeys.append( transformKey( fullKey, newkey ) );
259+
}
260+
mOldSettings->endGroup();
261+
return foundKeys;
262+
}
263+
264+
QPair<QString, QString> Qgs2To3Migration::transformKey( QString fullOldKey, QString newKeyPart )
265+
{
266+
QString newKey = newKeyPart;
267+
QString oldKey = fullOldKey;
268+
269+
if ( newKeyPart == QStringLiteral( "*" ) )
270+
{
271+
newKey = fullOldKey;
272+
}
273+
274+
if ( newKeyPart.endsWith( "/*" ) )
275+
{
276+
QStringList newKeyparts = newKeyPart.split( "/" );
277+
// Throw away the *
278+
newKeyparts.removeLast();
279+
QStringList oldKeyParts = fullOldKey.split( "/" );
280+
for ( int i = 0; i < newKeyparts.count(); ++i )
281+
{
282+
oldKeyParts.replace( i, newKeyparts.at( i ) );
283+
}
284+
newKey = oldKeyParts.join( "/" );
285+
}
286+
287+
return qMakePair( oldKey, newKey );
288+
}
289+
290+
QString Qgs2To3Migration::migrationFilePath()
291+
{
292+
return QgsApplication::pkgDataPath() + "/resources/2to3migration.txt";
293+
}

‎src/app/qgsversionmigration.h

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/***************************************************************************
2+
qgsversionmigration.h - QgsVersionMigration
3+
4+
---------------------
5+
begin : 30.7.2017
6+
copyright : (C) 2017 by nathan
7+
email : woodrow.nathan at gmail dot com
8+
***************************************************************************
9+
* *
10+
* This program is free software; you can redistribute it and/or modify *
11+
* it under the terms of the GNU General Public License as published by *
12+
* the Free Software Foundation; either version 2 of the License, or *
13+
* (at your option) any later version. *
14+
* *
15+
***************************************************************************/
16+
#ifndef QGSVERSIONMIGRATION_H
17+
#define QGSVERSIONMIGRATION_H
18+
19+
#include "qgis_app.h"
20+
#include "qgserror.h"
21+
22+
#include <QSettings>
23+
24+
class QgsSettings;
25+
26+
/**
27+
* Version migration class used to transfer settings, etc between major versions.
28+
* \note Not everything can be translated and depends on the from and to versions.
29+
*/
30+
class APP_EXPORT QgsVersionMigration
31+
{
32+
public:
33+
QgsVersionMigration();
34+
virtual ~QgsVersionMigration();
35+
36+
/**
37+
* Check if two version has a migration options.
38+
* @param fromVersion The version migrating from.
39+
* @param toVersion The version migrating to.
40+
* @return
41+
*/
42+
static QgsVersionMigration *canMigrate( int fromVersion, int toVersion );
43+
44+
/**
45+
* Run the version migration to convert between versions.
46+
* @return QgsError containing any error messages when running the conversion.
47+
*/
48+
virtual QgsError runMigration() = 0;
49+
virtual bool requiresMigration() = 0;
50+
};
51+
52+
53+
class Qgs2To3Migration : public QgsVersionMigration
54+
{
55+
public:
56+
virtual QgsError runMigration() override;
57+
virtual bool requiresMigration() override;
58+
private:
59+
QgsError migrateStyles();
60+
QgsError migrateSettings();
61+
62+
QList<QPair<QString, QString>> walk( QString group, QString newkey );
63+
QPair<QString, QString> transformKey( QString fullOldKey, QString newKeyPart );
64+
65+
QString migrationFilePath();
66+
67+
int mMigrationFileVersion;
68+
69+
QSettings *mOldSettings;
70+
71+
};
72+
73+
#endif // QGSVERSIONMIGRATION_H

‎src/core/symbology/qgsstyle.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ bool QgsStyle::load( const QString &filename )
362362
// Make sure there are no Null fields in parenting symbols and groups
363363
char *query = sqlite3_mprintf( "UPDATE symbol SET favorite=0 WHERE favorite IS NULL;"
364364
"UPDATE colorramp SET favorite=0 WHERE favorite IS NULL;"
365-
"UPDATE symgroup SET parent=0 WHERE parent IS NULL;" );
365+
);
366366
runEmptyQuery( query );
367367

368368
// First create all the main symbols

0 commit comments

Comments
 (0)
Please sign in to comment.