1 /***************************************************************************
2 qgsuserprofilemanager.cpp
3 --------------------------------------
4 Date : Jul-2017
5 Copyright : (C) 2017 by Nathan Woodrow
6 Email : woodrow.nathan at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16 #include "qgsuserprofilemanager.h"
17 #include "qgsuserprofile.h"
18 #include "qgsapplication.h"
19 #include "qgslogger.h"
20 #include "qgssettings.h"
21
22 #include <QFile>
23 #include <QDir>
24 #include <QTextStream>
25 #include <QProcess>
26 #include <QStandardPaths>
27
28
QgsUserProfileManager(const QString & rootLocation,QObject * parent)29 QgsUserProfileManager::QgsUserProfileManager( const QString &rootLocation, QObject *parent )
30 : QObject( parent )
31 {
32 setRootLocation( rootLocation );
33 }
34
resolveProfilesFolder(const QString & basePath)35 QString QgsUserProfileManager::resolveProfilesFolder( const QString &basePath )
36 {
37 return basePath + QDir::separator() + "profiles";
38 }
39
getProfile(const QString & defaultProfile,bool createNew,bool initSettings)40 QgsUserProfile *QgsUserProfileManager::getProfile( const QString &defaultProfile, bool createNew, bool initSettings )
41 {
42 QString profileName = defaultProfile.isEmpty() ? defaultProfileName() : defaultProfile;
43
44 if ( createNew && !profileExists( defaultProfile ) )
45 {
46 createUserProfile( profileName );
47 }
48
49 QgsUserProfile *profile = profileForName( profileName );
50 if ( initSettings )
51 profile->initSettings();
52
53 return profile;
54 }
55
setRootLocation(const QString & rootProfileLocation)56 void QgsUserProfileManager::setRootLocation( const QString &rootProfileLocation )
57 {
58 mRootProfilePath = rootProfileLocation;
59
60 //updates (or removes) profile file watcher for new root location
61 setNewProfileNotificationEnabled( mWatchProfiles );
62
63 mSettings.reset( new QSettings( settingsFile(), QSettings::IniFormat ) );
64 }
65
setNewProfileNotificationEnabled(bool enabled)66 void QgsUserProfileManager::setNewProfileNotificationEnabled( bool enabled )
67 {
68 mWatchProfiles = enabled;
69 if ( mWatchProfiles && !mRootProfilePath.isEmpty() && QDir( mRootProfilePath ).exists() )
70 {
71 mWatcher.reset( new QFileSystemWatcher() );
72 mWatcher->addPath( mRootProfilePath );
73 connect( mWatcher.get(), &QFileSystemWatcher::directoryChanged, this, [this]
74 {
75 emit profilesChanged();
76 } );
77 }
78 else
79 {
80 mWatcher.reset();
81 }
82 }
83
isNewProfileNotificationEnabled() const84 bool QgsUserProfileManager::isNewProfileNotificationEnabled() const
85 {
86 return static_cast< bool >( mWatcher.get() );
87 }
88
rootLocationIsSet() const89 bool QgsUserProfileManager::rootLocationIsSet() const
90 {
91 return !mRootProfilePath.isEmpty();
92 }
93
defaultProfileName() const94 QString QgsUserProfileManager::defaultProfileName() const
95 {
96 QString defaultName = QStringLiteral( "default" );
97 // If the profiles.ini doesn't have the default profile we grab it from
98 // global settings as it might be set by the admin.
99 // If the overrideProfile flag is set then no matter what the profiles.ini says we always take the
100 // global profile.
101 QgsSettings globalSettings;
102 if ( !mSettings->contains( QStringLiteral( "/core/defaultProfile" ) ) || globalSettings.value( QStringLiteral( "overrideLocalProfile" ), false, QgsSettings::Core ).toBool() )
103 {
104 return globalSettings.value( QStringLiteral( "defaultProfile" ), defaultName, QgsSettings::Core ).toString();
105 }
106 return mSettings->value( QStringLiteral( "/core/defaultProfile" ), defaultName ).toString();
107 }
108
setDefaultProfileName(const QString & name)109 void QgsUserProfileManager::setDefaultProfileName( const QString &name )
110 {
111 mSettings->setValue( QStringLiteral( "/core/defaultProfile" ), name );
112 mSettings->sync();
113 }
114
setDefaultFromActive()115 void QgsUserProfileManager::setDefaultFromActive()
116 {
117 setDefaultProfileName( userProfile()->name() );
118 }
119
allProfiles() const120 QStringList QgsUserProfileManager::allProfiles() const
121 {
122 return QDir( mRootProfilePath ).entryList( QDir::Dirs | QDir::NoDotAndDotDot );
123 }
124
profileExists(const QString & name) const125 bool QgsUserProfileManager::profileExists( const QString &name ) const
126 {
127 return allProfiles().contains( name );
128 }
129
profileForName(const QString & name) const130 QgsUserProfile *QgsUserProfileManager::profileForName( const QString &name ) const
131 {
132 QString profilePath = mRootProfilePath + QDir::separator() + name;
133 return new QgsUserProfile( profilePath );
134 }
135
createUserProfile(const QString & name)136 QgsError QgsUserProfileManager::createUserProfile( const QString &name )
137 {
138 QgsError error;
139
140 // TODO Replace with safe folder name
141
142 QDir folder( mRootProfilePath + QDir::separator() + name );
143 if ( !folder.exists() )
144 {
145 QDir().mkpath( folder.absolutePath() );
146 }
147
148 QFile qgisPrivateDbFile( folder.absolutePath() + QDir::separator() + "qgis.db" );
149
150 // first we look for ~/.qgis/qgis.db
151 if ( !qgisPrivateDbFile.exists() )
152 {
153 // if it doesn't exist we copy it from the global resources dir
154 QString qgisMasterDbFileName = QgsApplication::qgisMasterDatabaseFilePath();
155 QFile masterFile( qgisMasterDbFileName );
156
157 //now copy the master file into the users .qgis dir
158 masterFile.copy( qgisPrivateDbFile.fileName() );
159
160 // In some packaging systems, the master can be read-only. Make sure to make
161 // the copy user writable.
162 const QFile::Permissions perms = QFile( qgisPrivateDbFile.fileName() ).permissions();
163 if ( !( perms & QFile::WriteOwner ) )
164 {
165 if ( !qgisPrivateDbFile.setPermissions( perms | QFile::WriteOwner ) )
166 {
167 error.append( tr( "Can not make '%1' user writable" ).arg( qgisPrivateDbFile.fileName() ) );
168 }
169 }
170 }
171
172 if ( error.isEmpty() )
173 {
174 emit profilesChanged();
175 }
176
177 return error;
178 }
179
deleteProfile(const QString & name)180 QgsError QgsUserProfileManager::deleteProfile( const QString &name )
181 {
182 QgsError error;
183 QDir folder( mRootProfilePath + QDir::separator() + name );
184
185 // This might have to be changed to something better.
186 bool deleted = folder.removeRecursively();
187 if ( !deleted )
188 {
189 error.append( ( tr( "Unable to fully delete user profile folder" ) ) );
190 }
191 else
192 {
193 emit profilesChanged();
194 }
195 return error;
196 }
197
settingsFile() const198 QString QgsUserProfileManager::settingsFile() const
199 {
200 return mRootProfilePath + QDir::separator() + "profiles.ini";
201 }
202
userProfile()203 QgsUserProfile *QgsUserProfileManager::userProfile()
204 {
205 return mUserProfile.get();
206 }
207
loadUserProfile(const QString & name)208 void QgsUserProfileManager::loadUserProfile( const QString &name )
209 {
210 #if QT_CONFIG(process)
211 QString path = QDir::toNativeSeparators( QCoreApplication::applicationFilePath() );
212 QStringList arguments;
213 arguments << QCoreApplication::arguments();
214 // The first is the path to the application
215 // on Windows this might not be case so we need to handle that
216 // http://doc.qt.io/qt-5/qcoreapplication.html#arguments
217 arguments.removeFirst();
218 arguments << QStringLiteral( "--profile" ) << name;
219 QgsDebugMsg( QStringLiteral( "Starting instance from %1 with %2" ).arg( path ).arg( arguments.join( " " ) ) );
220 QProcess::startDetached( path, arguments, QDir::toNativeSeparators( QCoreApplication::applicationDirPath() ) );
221 #else
222 Q_UNUSED( name )
223 Q_ASSERT( "Starting the user profile is not supported on the platform" );
224 #endif //QT_CONFIG(process)
225 }
226
setActiveUserProfile(const QString & profile)227 void QgsUserProfileManager::setActiveUserProfile( const QString &profile )
228 {
229 if ( ! mUserProfile.get() )
230 {
231 mUserProfile.reset( profileForName( profile ) );
232 }
233 }
234