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