akonadi
firstrun.cpp
00001 /* 00002 Copyright (c) 2008 Volker Krause <vkrause@kde.org> 00003 00004 This library is free software; you can redistribute it and/or modify it 00005 under the terms of the GNU Library General Public License as published by 00006 the Free Software Foundation; either version 2 of the License, or (at your 00007 option) any later version. 00008 00009 This library is distributed in the hope that it will be useful, but WITHOUT 00010 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 00011 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public 00012 License for more details. 00013 00014 You should have received a copy of the GNU Library General Public License 00015 along with this library; see the file COPYING.LIB. If not, write to the 00016 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 00017 02110-1301, USA. 00018 */ 00019 00020 #include "firstrun_p.h" 00021 #include "dbusconnectionpool.h" 00022 00023 #include <akonadi/agentinstance.h> 00024 #include <akonadi/agentinstancecreatejob.h> 00025 #include <akonadi/agentmanager.h> 00026 #include <akonadi/agenttype.h> 00027 00028 #include <KConfig> 00029 #include <KConfigGroup> 00030 #include <KDebug> 00031 #include <KGlobal> 00032 #include <KProcess> 00033 #include <KStandardDirs> 00034 00035 #include <QtDBus/QDBusConnection> 00036 #include <QtDBus/QDBusInterface> 00037 #include <QtDBus/QDBusReply> 00038 #include <QtCore/QDir> 00039 #include <QtCore/QMetaMethod> 00040 #include <QtCore/QMetaObject> 00041 00042 static char FIRSTRUN_DBUSLOCK[] = "org.kde.Akonadi.Firstrun.lock"; 00043 00044 using namespace Akonadi; 00045 00046 Firstrun::Firstrun( QObject *parent ) 00047 : QObject( parent ), 00048 mConfig( new KConfig( QLatin1String( "akonadi-firstrunrc" ) ) ), 00049 mCurrentDefault( 0 ), 00050 mProcess( 0 ) 00051 { 00052 kDebug(); 00053 if ( DBusConnectionPool::threadConnection().registerService( QLatin1String( FIRSTRUN_DBUSLOCK ) ) ) { 00054 findPendingDefaults(); 00055 kDebug() << mPendingDefaults; 00056 setupNext(); 00057 } else { 00058 kDebug() << "D-Bus lock found, so someone else does the work for us already."; 00059 deleteLater(); 00060 } 00061 } 00062 00063 Firstrun::~Firstrun() 00064 { 00065 DBusConnectionPool::threadConnection().unregisterService( QLatin1String( FIRSTRUN_DBUSLOCK ) ); 00066 delete mConfig; 00067 kDebug() << "done"; 00068 } 00069 00070 void Firstrun::findPendingDefaults() 00071 { 00072 const KConfigGroup cfg( mConfig, "ProcessedDefaults" ); 00073 foreach ( const QString &dirName, KGlobal::dirs()->findDirs( "data", QLatin1String( "akonadi/firstrun" ) ) ) { 00074 const QStringList files = QDir( dirName ).entryList( QDir::Files | QDir::Readable ); 00075 foreach ( const QString &fileName, files ) { 00076 const QString fullName = dirName + fileName; 00077 KConfig c( fullName ); 00078 const QString id = KConfigGroup( &c, "Agent" ).readEntry( "Id", QString() ); 00079 if ( id.isEmpty() ) { 00080 kWarning() << "Found invalid default configuration in " << fullName; 00081 continue; 00082 } 00083 if ( cfg.hasKey( id ) ) 00084 continue; 00085 mPendingDefaults << dirName + fileName; 00086 } 00087 } 00088 } 00089 00090 #ifndef KDEPIM_NO_KRESOURCES 00091 static QString resourceTypeForMimetype( const QStringList &mimeTypes ) 00092 { 00093 if ( mimeTypes.contains( QLatin1String( "text/directory" ) ) ) 00094 return QString::fromLatin1( "contact" ); 00095 if ( mimeTypes.contains( QLatin1String( "text/calendar" ) ) ) 00096 return QString::fromLatin1( "calendar" ); 00097 // TODO notes 00098 return QString(); 00099 } 00100 00101 void Firstrun::migrateKresType( const QString& resourceFamily ) 00102 { 00103 mResourceFamily = resourceFamily; 00104 KConfig config( QLatin1String( "kres-migratorrc" ) ); 00105 KConfigGroup migrationCfg( &config, "Migration" ); 00106 const bool enabled = migrationCfg.readEntry( "Enabled", false ); 00107 const bool setupClientBridge = migrationCfg.readEntry( "SetupClientBridge", true ); 00108 const int currentVersion = migrationCfg.readEntry( QString::fromLatin1( "Version-%1" ).arg( resourceFamily ), 0 ); 00109 const int targetVersion = migrationCfg.readEntry( "TargetVersion", 0 ); 00110 if ( enabled && currentVersion < targetVersion ) { 00111 kDebug() << "Performing migration of legacy KResource settings. Good luck!"; 00112 mProcess = new KProcess( this ); 00113 connect( mProcess, SIGNAL( finished( int ) ), SLOT( migrationFinished( int ) ) ); 00114 QStringList args = QStringList() << QLatin1String( "--interactive-on-change" ) 00115 << QLatin1String( "--type" ) << resourceFamily; 00116 if ( !setupClientBridge ) 00117 args << QLatin1String( "--omit-client-bridge" ); 00118 mProcess->setProgram( QLatin1String( "kres-migrator" ), args ); 00119 mProcess->start(); 00120 if ( !mProcess->waitForStarted() ) 00121 migrationFinished( -1 ); 00122 } else { 00123 // nothing to do 00124 setupNext(); 00125 } 00126 } 00127 00128 void Firstrun::migrationFinished( int exitCode ) 00129 { 00130 Q_ASSERT( mProcess ); 00131 if ( exitCode == 0 ) { 00132 kDebug() << "KResource -> Akonadi migration has been successful"; 00133 KConfig config( QLatin1String( "kres-migratorrc" ) ); 00134 KConfigGroup migrationCfg( &config, "Migration" ); 00135 const int targetVersion = migrationCfg.readEntry( "TargetVersion", 0 ); 00136 migrationCfg.writeEntry( QString::fromLatin1( "Version-%1" ).arg( mResourceFamily ), targetVersion ); 00137 migrationCfg.sync(); 00138 } else if ( exitCode != 1 ) { 00139 // exit code 1 means it is already running, so we are probably called by a migrator instance 00140 kError() << "KResource -> Akonadi migration failed!"; 00141 kError() << "command was: " << mProcess->program(); 00142 kError() << "exit code: " << mProcess->exitCode(); 00143 kError() << "stdout: " << mProcess->readAllStandardOutput(); 00144 kError() << "stderr: " << mProcess->readAllStandardError(); 00145 } 00146 00147 setupNext(); 00148 } 00149 #endif 00150 00151 00152 void Firstrun::setupNext() 00153 { 00154 delete mCurrentDefault; 00155 mCurrentDefault = 0; 00156 00157 if ( mPendingDefaults.isEmpty() ) { 00158 deleteLater(); 00159 return; 00160 } 00161 00162 mCurrentDefault = new KConfig( mPendingDefaults.takeFirst() ); 00163 const KConfigGroup agentCfg = KConfigGroup( mCurrentDefault, "Agent" ); 00164 00165 AgentType type = AgentManager::self()->type( agentCfg.readEntry( "Type", QString() ) ); 00166 if ( !type.isValid() ) { 00167 kError() << "Unable to obtain agent type for default resource agent configuration " << mCurrentDefault->name(); 00168 setupNext(); 00169 return; 00170 } 00171 00172 #ifndef KDEPIM_NO_KRESOURCES 00173 // KDE5: remove me 00174 // check if there is a kresource setup for this type already 00175 const QString kresType = resourceTypeForMimetype( type.mimeTypes() ); 00176 if ( !kresType.isEmpty() ) { 00177 const QString kresCfgFile = KStandardDirs::locateLocal( "config", QString::fromLatin1( "kresources/%1/stdrc" ).arg( kresType ) ); 00178 KConfig resCfg( kresCfgFile ); 00179 const KConfigGroup resGroup( &resCfg, "General" ); 00180 bool legacyResourceFound = false; 00181 const QStringList kresResources = resGroup.readEntry( "ResourceKeys", QStringList() ) 00182 + resGroup.readEntry( "PassiveResourceKeys", QStringList() ); 00183 foreach ( const QString &kresResource, kresResources ) { 00184 const KConfigGroup cfg( &resCfg, QString::fromLatin1( "Resource_%1" ).arg( kresResource ) ); 00185 if ( cfg.readEntry( "ResourceType", QString() ) != QLatin1String( "akonadi" ) ) { // not a bridge 00186 legacyResourceFound = true; 00187 break; 00188 } 00189 } 00190 if ( legacyResourceFound ) { 00191 kDebug() << "ignoring " << mCurrentDefault->name() << " as there is a KResource setup for its type already."; 00192 KConfigGroup cfg( mConfig, "ProcessedDefaults" ); 00193 cfg.writeEntry( agentCfg.readEntry( "Id", QString() ), QString::fromLatin1( "kres" ) ); 00194 cfg.sync(); 00195 migrateKresType( kresType ); 00196 return; 00197 } 00198 } 00199 #endif 00200 00201 AgentInstanceCreateJob *job = new AgentInstanceCreateJob( type ); 00202 connect( job, SIGNAL( result( KJob* ) ), SLOT( instanceCreated( KJob* ) ) ); 00203 job->start(); 00204 } 00205 00206 void Firstrun::instanceCreated( KJob *job ) 00207 { 00208 Q_ASSERT( mCurrentDefault ); 00209 00210 if ( job->error() ) { 00211 kError() << "Creating agent instance failed for " << mCurrentDefault->name(); 00212 setupNext(); 00213 return; 00214 } 00215 00216 AgentInstance instance = static_cast<AgentInstanceCreateJob*>( job )->instance(); 00217 const KConfigGroup agentCfg = KConfigGroup( mCurrentDefault, "Agent" ); 00218 const QString agentName = agentCfg.readEntry( "Name", QString() ); 00219 if ( !agentName.isEmpty() ) 00220 instance.setName( agentName ); 00221 00222 // agent specific settings, using the D-Bus <-> KConfigXT bridge 00223 const KConfigGroup settings = KConfigGroup( mCurrentDefault, "Settings" ); 00224 00225 QDBusInterface *iface = new QDBusInterface( QString::fromLatin1( "org.freedesktop.Akonadi.Agent.%1" ).arg( instance.identifier() ), 00226 QLatin1String( "/Settings" ), QString(), 00227 DBusConnectionPool::threadConnection(), this ); 00228 if ( !iface->isValid() ) { 00229 kError() << "Unable to obtain the KConfigXT D-Bus interface of " << instance.identifier(); 00230 setupNext(); 00231 delete iface; 00232 return; 00233 } 00234 00235 foreach ( const QString &setting, settings.keyList() ) { 00236 kDebug() << "Setting up " << setting << " for agent " << instance.identifier(); 00237 const QString methodName = QString::fromLatin1( "set%1" ).arg( setting ); 00238 const QVariant::Type argType = argumentType( iface->metaObject(), methodName ); 00239 if ( argType == QVariant::Invalid ) { 00240 kError() << "Setting " << setting << " not found in agent configuration interface of " << instance.identifier(); 00241 continue; 00242 } 00243 00244 QVariant arg; 00245 if ( argType == QVariant::String ) { 00246 // Since a string could be a path we always use readPathEntry here, 00247 // that shouldn't harm any normal string settings 00248 arg = settings.readPathEntry( setting, QString() ); 00249 } else 00250 arg = settings.readEntry( setting, QVariant( argType ) ); 00251 00252 const QDBusReply<void> reply = iface->call( methodName, arg ); 00253 if ( !reply.isValid() ) 00254 kError() << "Setting " << setting << " failed for agent " << instance.identifier(); 00255 } 00256 00257 instance.reconfigure(); 00258 instance.synchronize(); 00259 delete iface; 00260 00261 // remember we set this one up already 00262 KConfigGroup cfg( mConfig, "ProcessedDefaults" ); 00263 cfg.writeEntry( agentCfg.readEntry( "Id", QString() ), instance.identifier() ); 00264 cfg.sync(); 00265 00266 setupNext(); 00267 } 00268 00269 QVariant::Type Firstrun::argumentType( const QMetaObject *mo, const QString &method ) 00270 { 00271 QMetaMethod m; 00272 for ( int i = 0; i < mo->methodCount(); ++i ) { 00273 const QString signature = QString::fromLatin1( mo->method( i ).signature() ); 00274 if ( signature.startsWith( method ) ) 00275 m = mo->method( i ); 00276 } 00277 00278 if ( !m.signature() ) 00279 return QVariant::Invalid; 00280 00281 const QList<QByteArray> argTypes = m.parameterTypes(); 00282 if ( argTypes.count() != 1 ) 00283 return QVariant::Invalid; 00284 00285 return QVariant::nameToType( argTypes.first() ); 00286 } 00287 00288 #include "firstrun_p.moc"