1 /***************************************************************************
2 qgsversionmigration.cpp - QgsVersionMigration
3
4 ---------------------
5 begin : 30.7.2017
6 copyright : (C) 2017 by Nathan Woodrow
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 #include "qgsuserprofilemanager.h"
27
28 #include <QFile>
29 #include <QTextStream>
30 #include <QDir>
31 #include <QSqlDatabase>
32 #include <QSqlQuery>
33 #include <QSqlError>
34 #include <QDomDocument>
35
canMigrate(int fromVersion,int toVersion)36 std::unique_ptr<QgsVersionMigration> QgsVersionMigration::canMigrate( int fromVersion, int toVersion )
37 {
38 if ( fromVersion == 20000 && toVersion >= 29900 )
39 {
40 return qgis::make_unique< Qgs2To3Migration >();
41 }
42 return nullptr;
43 }
44
runMigration()45 QgsError Qgs2To3Migration::runMigration()
46 {
47 QgsError errors;
48 QgsError settingsErrors = migrateSettings();
49 if ( !settingsErrors.isEmpty() )
50 {
51 const QList<QgsErrorMessage> errorList( settingsErrors.messageList( ) );
52 for ( const auto &err : errorList )
53 {
54 errors.append( err );
55 }
56 }
57 QgsError stylesErrors = migrateStyles();
58 if ( !stylesErrors.isEmpty() )
59 {
60 const QList<QgsErrorMessage> errorList( stylesErrors.messageList( ) );
61 for ( const auto &err : errorList )
62 {
63 errors.append( err );
64 }
65 }
66 QgsError authDbErrors = migrateAuthDb();
67 if ( !authDbErrors.isEmpty() )
68 {
69 const QList<QgsErrorMessage> errorList( authDbErrors.messageList( ) );
70 for ( const auto &err : errorList )
71 {
72 errors.append( err );
73 }
74 }
75 return errors;
76 }
77
requiresMigration()78 bool Qgs2To3Migration::requiresMigration()
79 {
80 QgsSettings settings;
81 bool alreadyMigrated = settings.value( QStringLiteral( "migration/settings" ), false ).toBool();
82 int settingsMigrationVersion = settings.value( QStringLiteral( "migration/fileVersion" ), 0 ).toInt();
83 QFile migrationFile( migrationFilePath() );
84 if ( migrationFile.open( QIODevice::ReadOnly | QIODevice::Text ) )
85 {
86 QTextStream in( &migrationFile );
87 QString line = in.readLine();
88 if ( line.startsWith( "#" ) && line.contains( QStringLiteral( "version=" ) ) )
89 {
90 QStringList parts = line.split( '=' );
91 mMigrationFileVersion = parts.at( 1 ).toInt();
92 QgsDebugMsgLevel( QStringLiteral( "File version is=%1" ).arg( mMigrationFileVersion ), 2 );
93 }
94 migrationFile.close();
95 }
96 else
97 {
98 QgsDebugMsg( QStringLiteral( "Can not open %1" ).arg( migrationFile.fileName() ) );
99 mMigrationFileVersion = settingsMigrationVersion;
100 }
101
102 return ( !alreadyMigrated || settingsMigrationVersion != mMigrationFileVersion );
103 }
104
migrateStyles()105 QgsError Qgs2To3Migration::migrateStyles()
106 {
107 QgsError error;
108 QString oldHome = QStringLiteral( "%1/.qgis2" ).arg( QDir::homePath() );
109 QString oldStyleFile = QStringLiteral( "%1/symbology-ng-style.db" ).arg( oldHome );
110 QgsDebugMsgLevel( QStringLiteral( "OLD STYLE FILE %1" ).arg( oldStyleFile ), 2 );
111 QSqlDatabase db = QSqlDatabase::addDatabase( "QSQLITE", "migration" );
112 db.setDatabaseName( oldStyleFile );
113 if ( !db.open() )
114 {
115 error.append( db.lastError().text() );
116 QgsDebugMsg( db.lastError().text() );
117 return error;
118 }
119
120 QSqlQuery query( db );
121 QSqlQuery tagQuery( "SELECT name FROM tag"
122 "JOIN tagmap ON tagmap.tag_id = tag.id"
123 "WHERE tagmap.symbol_id = :symbol_id", db );
124
125 QgsStyle *style = QgsStyle::defaultStyle();
126 if ( query.exec( "SELECT id, name, xml FROM symbol" ) )
127 {
128 while ( query.next() )
129 {
130 QString symbol_id = query.value( 0 ).toString();
131 QString name = query.value( 1 ).toString();
132 QString xml = query.value( 2 ).toString();
133 QDomDocument doc;
134 if ( !doc.setContent( xml ) )
135 {
136 QgsDebugMsg( "Cannot open symbol " + name );
137 continue;
138 }
139
140 tagQuery.bindValue( ":symbol_id", symbol_id );
141
142 QStringList tags;
143 if ( tagQuery.exec() )
144 {
145 while ( query.next() )
146 {
147 QString tagname = query.value( 0 ).toString();
148 tags << tagname;
149 }
150 }
151
152 QDomElement symElement = doc.documentElement();
153 QgsDebugMsgLevel( QStringLiteral( "MIGRATION: Importing %1" ).arg( name ), 2 );
154 QgsSymbol *symbol = QgsSymbolLayerUtils::loadSymbol( symElement, QgsReadWriteContext() );
155 tags << "QGIS 2";
156 if ( style->symbolId( name ) == 0 )
157 {
158 style->saveSymbol( name, symbol, false, tags );
159 }
160 }
161 }
162
163 QgsDebugMsgLevel( oldStyleFile, 2 );
164 return error;
165 }
166
migrateSettings()167 QgsError Qgs2To3Migration::migrateSettings()
168 {
169 QgsError error;
170
171 QgsSettings newSettings;
172
173 // The platform default location for the settings from 2.x
174 mOldSettings = new QSettings( "QGIS", "QGIS2" );
175
176 QFile inputFile( migrationFilePath() );
177 std::map<QString, QgsSettings::Section> sections;
178 sections["none"] = QgsSettings::NoSection;
179 sections["core"] = QgsSettings::Core;
180 sections["gui"] = QgsSettings::Gui;
181 sections["server"] = QgsSettings::Server;
182 sections["plugins"] = QgsSettings::Plugins;
183 sections["auth"] = QgsSettings::Auth;
184 sections["app"] = QgsSettings::App;
185 sections["providers"] = QgsSettings::Providers;
186 sections["misc"] = QgsSettings::Misc;
187
188 QList<QPair<QString, QString>> keys;
189
190 if ( inputFile.open( QIODevice::ReadOnly | QIODevice::Text ) )
191 {
192 QTextStream in( &inputFile );
193 while ( !in.atEnd() )
194 {
195 QString line = in.readLine();
196
197 if ( line.startsWith( "#" ) )
198 continue;
199
200 if ( line.isEmpty() )
201 continue;
202
203 const QStringList parts = line.split( ';' );
204
205 Q_ASSERT_X( parts.count() == 2, "QgsVersionMigration::migrateSettings()", "Can't split line in 2 parts." );
206
207 QString oldKey = parts.at( 0 );
208 QString newKey = parts.at( 1 );
209
210 if ( oldKey.endsWith( "/*" ) )
211 {
212 oldKey = oldKey.replace( "/*", "" );
213 QList<QPair<QString, QString>> keyList = walk( oldKey, newKey );
214 keys.append( keyList );
215 }
216 else
217 {
218 QPair<QString, QString> key = transformKey( oldKey, newKey );
219 keys.append( key );
220 }
221
222 }
223 inputFile.close();
224 newSettings.setValue( QStringLiteral( "migration/settings" ), true );
225 // Set the dev gen so we can force a migration.
226 newSettings.setValue( QStringLiteral( "migration/fileVersion" ), mMigrationFileVersion );
227 }
228 else
229 {
230 QString msg = QString( "Can not open %1" ).arg( inputFile.fileName() );
231 QgsDebugMsg( msg );
232 error.append( msg );
233 }
234
235 if ( keys.count() > 0 )
236 {
237 QgsDebugMsgLevel( QStringLiteral( "MIGRATION: Translating settings keys" ), 2 );
238 QList<QPair<QString, QString>>::iterator i;
239 for ( i = keys.begin(); i != keys.end(); ++i )
240 {
241 QPair<QString, QString> pair = *i;
242
243 QString oldKey = pair.first;
244 QString newKey = pair.second;
245
246 if ( oldKey.contains( oldKey ) )
247 {
248 QgsDebugMsgLevel( QStringLiteral( " -> %1 -> %2" ).arg( oldKey, newKey ), 2 );
249 newSettings.setValue( newKey, mOldSettings->value( oldKey ) );
250 }
251 }
252 }
253 return error;
254 }
255
migrateAuthDb()256 QgsError Qgs2To3Migration::migrateAuthDb()
257 {
258 QgsError error;
259 QString oldHome = QStringLiteral( "%1/.qgis2" ).arg( QDir::homePath() );
260 QString oldAuthDbFilePath = QStringLiteral( "%1/qgis-auth.db" ).arg( oldHome );
261 // Try to retrieve the current profile folder (I didn't find an QgsApplication API for it)
262 QDir settingsDir = QFileInfo( QgsSettings().fileName() ).absoluteDir();
263 settingsDir.cdUp();
264 QString newAuthDbFilePath = QStringLiteral( "%1/qgis-auth.db" ).arg( settingsDir.absolutePath() );
265 // Do not overwrite!
266 if ( QFile( newAuthDbFilePath ).exists( ) )
267 {
268 QString msg = QStringLiteral( "Could not copy old auth DB to %1: file already exists!" ).arg( newAuthDbFilePath );
269 QgsDebugMsg( msg );
270 error.append( msg );
271 }
272 else
273 {
274 QFile oldDbFile( oldAuthDbFilePath );
275 if ( oldDbFile.exists( ) )
276 {
277 if ( oldDbFile.copy( newAuthDbFilePath ) )
278 {
279 QgsDebugMsgLevel( QStringLiteral( "Old auth DB successfully copied to %1" ).arg( newAuthDbFilePath ), 2 );
280 }
281 else
282 {
283 QString msg = QStringLiteral( "Could not copy auth DB %1 to %2" ).arg( oldAuthDbFilePath, newAuthDbFilePath );
284 QgsDebugMsg( msg );
285 error.append( msg );
286 }
287 }
288 else
289 {
290 QString msg = QStringLiteral( "Could not copy auth DB %1 to %2: old DB does not exists!" ).arg( oldAuthDbFilePath, newAuthDbFilePath );
291 QgsDebugMsg( msg );
292 error.append( msg );
293 }
294 }
295 return error;
296 }
297
walk(QString group,QString newkey)298 QList<QPair<QString, QString> > Qgs2To3Migration::walk( QString group, QString newkey )
299 {
300 mOldSettings->beginGroup( group );
301 QList<QPair<QString, QString> > foundKeys;
302 const auto constChildGroups = mOldSettings->childGroups();
303 for ( const QString &group : constChildGroups )
304 {
305 QList<QPair<QString, QString> > data = walk( group, newkey );
306 foundKeys.append( data );
307 }
308
309 const auto constChildKeys = mOldSettings->childKeys();
310 for ( const QString &key : constChildKeys )
311 {
312 QString fullKey = mOldSettings->group() + "/" + key;
313 foundKeys.append( transformKey( fullKey, newkey ) );
314 }
315 mOldSettings->endGroup();
316 return foundKeys;
317 }
318
transformKey(QString fullOldKey,QString newKeyPart)319 QPair<QString, QString> Qgs2To3Migration::transformKey( QString fullOldKey, QString newKeyPart )
320 {
321 QString newKey = newKeyPart;
322 QString oldKey = fullOldKey;
323
324 if ( newKeyPart == QLatin1String( "*" ) )
325 {
326 newKey = fullOldKey;
327 }
328
329 if ( newKeyPart.endsWith( "/*" ) )
330 {
331 QStringList newKeyparts = newKeyPart.split( '/' );
332 // Throw away the *
333 newKeyparts.removeLast();
334 QStringList oldKeyParts = fullOldKey.split( '/' );
335 for ( int i = 0; i < newKeyparts.count(); ++i )
336 {
337 oldKeyParts.replace( i, newKeyparts.at( i ) );
338 }
339 newKey = oldKeyParts.join( "/" );
340 }
341
342 return qMakePair( oldKey, newKey );
343 }
344
migrationFilePath()345 QString Qgs2To3Migration::migrationFilePath()
346 {
347 return QgsApplication::resolvePkgPath() + "/resources/2to3migration.txt";
348 }
349