1 | /* |
2 | Copyright (c) 2008 Volker Krause <vkrause@kde.org> |
3 | |
4 | This library is free software; you can redistribute it and/or modify it |
5 | under the terms of the GNU Library General Public License as published by |
6 | the Free Software Foundation; either version 2 of the License, or (at your |
7 | option) any later version. |
8 | |
9 | This library is distributed in the hope that it will be useful, but WITHOUT |
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public |
12 | License for more details. |
13 | |
14 | You should have received a copy of the GNU Library General Public License |
15 | along with this library; see the file COPYING.LIB. If not, write to the |
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
17 | 02110-1301, USA. |
18 | */ |
19 | |
20 | #include "firstrun_p.h" |
21 | #include "dbusconnectionpool.h" |
22 | #include "servermanager.h" |
23 | |
24 | #include <akonadi/agentinstance.h> |
25 | #include <akonadi/agentinstancecreatejob.h> |
26 | #include <akonadi/agentmanager.h> |
27 | #include <akonadi/agenttype.h> |
28 | |
29 | #include <KConfig> |
30 | #include <KConfigGroup> |
31 | #include <KDebug> |
32 | #include <KGlobal> |
33 | #include <KProcess> |
34 | #include <KStandardDirs> |
35 | |
36 | #include <QtDBus/QDBusConnection> |
37 | #include <QtDBus/QDBusInterface> |
38 | #include <QtDBus/QDBusReply> |
39 | #include <QtCore/QDir> |
40 | #include <QtCore/QMetaMethod> |
41 | #include <QtCore/QMetaObject> |
42 | |
43 | static char FIRSTRUN_DBUSLOCK[] = "org.kde.Akonadi.Firstrun.lock" ; |
44 | |
45 | using namespace Akonadi; |
46 | |
47 | Firstrun::Firstrun(QObject *parent) |
48 | : QObject(parent) |
49 | , mConfig(new KConfig(ServerManager::addNamespace(QLatin1String("akonadi-firstrunrc" )))) |
50 | , mCurrentDefault(0) |
51 | , mProcess(0) |
52 | { |
53 | //The code in firstrun is not safe in multi-instance mode |
54 | Q_ASSERT(!ServerManager::hasInstanceIdentifier()); |
55 | if (ServerManager::hasInstanceIdentifier()) { |
56 | deleteLater(); |
57 | return; |
58 | } |
59 | kDebug(); |
60 | if (DBusConnectionPool::threadConnection().registerService(QLatin1String(FIRSTRUN_DBUSLOCK))) { |
61 | findPendingDefaults(); |
62 | kDebug() << mPendingDefaults; |
63 | setupNext(); |
64 | } else { |
65 | kDebug() << "D-Bus lock found, so someone else does the work for us already." ; |
66 | deleteLater(); |
67 | } |
68 | } |
69 | |
70 | Firstrun::~Firstrun() |
71 | { |
72 | DBusConnectionPool::threadConnection().unregisterService(QLatin1String(FIRSTRUN_DBUSLOCK)); |
73 | delete mConfig; |
74 | kDebug() << "done" ; |
75 | } |
76 | |
77 | void Firstrun::findPendingDefaults() |
78 | { |
79 | const KConfigGroup cfg(mConfig, "ProcessedDefaults" ); |
80 | foreach (const QString &dirName, KGlobal::dirs()->findDirs("data" , QLatin1String("akonadi/firstrun" ))) { |
81 | const QStringList files = QDir(dirName).entryList(QDir::Files | QDir::Readable); |
82 | foreach (const QString &fileName, files) { |
83 | const QString fullName = dirName + fileName; |
84 | KConfig c(fullName); |
85 | const QString id = KConfigGroup(&c, "Agent" ).readEntry("Id" , QString()); |
86 | if (id.isEmpty()) { |
87 | kWarning() << "Found invalid default configuration in " << fullName; |
88 | continue; |
89 | } |
90 | if (cfg.hasKey(id)) { |
91 | continue; |
92 | } |
93 | mPendingDefaults << dirName + fileName; |
94 | } |
95 | } |
96 | |
97 | #ifndef KDEPIM_NO_KRESOURCES |
98 | // always check legacy kres for migration, their migrator might have changed again |
99 | mPendingKres << QLatin1String("contact" ) << QLatin1String("calendar" ); |
100 | #endif |
101 | } |
102 | |
103 | #ifndef KDEPIM_NO_KRESOURCES |
104 | static QString resourceTypeForMimetype(const QStringList &mimeTypes) |
105 | { |
106 | if (mimeTypes.contains(QLatin1String("text/directory" ))) { |
107 | return QString::fromLatin1("contact" ); |
108 | } |
109 | if (mimeTypes.contains(QLatin1String("text/calendar" ))) { |
110 | return QString::fromLatin1("calendar" ); |
111 | } |
112 | // TODO notes |
113 | return QString(); |
114 | } |
115 | |
116 | void Firstrun::migrateKresType(const QString &resourceFamily) |
117 | { |
118 | mResourceFamily = resourceFamily; |
119 | KConfig config(QLatin1String("kres-migratorrc" )); |
120 | KConfigGroup migrationCfg(&config, "Migration" ); |
121 | const bool enabled = migrationCfg.readEntry("Enabled" , false); |
122 | const bool setupClientBridge = migrationCfg.readEntry("SetupClientBridge" , true); |
123 | const int currentVersion = migrationCfg.readEntry(QString::fromLatin1("Version-%1" ).arg(resourceFamily), 0); |
124 | const int targetVersion = migrationCfg.readEntry("TargetVersion" , 0); |
125 | if (enabled && currentVersion < targetVersion) { |
126 | kDebug() << "Performing migration of legacy KResource settings. Good luck!" ; |
127 | mProcess = new KProcess(this); |
128 | connect(mProcess, SIGNAL(finished(int)), SLOT(migrationFinished(int))); |
129 | QStringList args = QStringList() << QLatin1String("--interactive-on-change" ) |
130 | << QLatin1String("--type" ) << resourceFamily; |
131 | if (!setupClientBridge) { |
132 | args << QLatin1String("--omit-client-bridge" ); |
133 | } |
134 | mProcess->setProgram(QLatin1String("kres-migrator" ), args); |
135 | mProcess->start(); |
136 | if (!mProcess->waitForStarted()) { |
137 | migrationFinished(-1); |
138 | } |
139 | } else { |
140 | // nothing to do |
141 | setupNext(); |
142 | } |
143 | } |
144 | |
145 | void Firstrun::migrationFinished(int exitCode) |
146 | { |
147 | Q_ASSERT(mProcess); |
148 | if (exitCode == 0) { |
149 | kDebug() << "KResource -> Akonadi migration has been successful" ; |
150 | KConfig config(QLatin1String("kres-migratorrc" )); |
151 | KConfigGroup migrationCfg(&config, "Migration" ); |
152 | const int targetVersion = migrationCfg.readEntry("TargetVersion" , 0); |
153 | migrationCfg.writeEntry(QString::fromLatin1("Version-%1" ).arg(mResourceFamily), targetVersion); |
154 | migrationCfg.sync(); |
155 | } else if (exitCode != 1) { |
156 | // exit code 1 means it is already running, so we are probably called by a migrator instance |
157 | kError() << "KResource -> Akonadi migration failed!" ; |
158 | kError() << "command was: " << mProcess->program(); |
159 | kError() << "exit code: " << mProcess->exitCode(); |
160 | kError() << "stdout: " << mProcess->readAllStandardOutput(); |
161 | kError() << "stderr: " << mProcess->readAllStandardError(); |
162 | } |
163 | |
164 | setupNext(); |
165 | } |
166 | #endif |
167 | |
168 | void Firstrun::setupNext() |
169 | { |
170 | delete mCurrentDefault; |
171 | mCurrentDefault = 0; |
172 | |
173 | if (mPendingDefaults.isEmpty()) { |
174 | #ifndef KDEPIM_NO_KRESOURCES |
175 | if (!mPendingKres.isEmpty()) { |
176 | migrateKresType(mPendingKres.takeFirst()); |
177 | return; |
178 | } |
179 | #endif |
180 | deleteLater(); |
181 | return; |
182 | } |
183 | |
184 | mCurrentDefault = new KConfig(mPendingDefaults.takeFirst()); |
185 | const KConfigGroup agentCfg = KConfigGroup(mCurrentDefault, "Agent" ); |
186 | |
187 | AgentType type = AgentManager::self()->type(agentCfg.readEntry("Type" , QString())); |
188 | if (!type.isValid()) { |
189 | kError() << "Unable to obtain agent type for default resource agent configuration " << mCurrentDefault->name(); |
190 | setupNext(); |
191 | return; |
192 | } |
193 | |
194 | #ifndef KDEPIM_NO_KRESOURCES |
195 | // KDE5: remove me |
196 | // check if there is a kresource setup for this type already |
197 | const QString kresType = resourceTypeForMimetype(type.mimeTypes()); |
198 | if (!kresType.isEmpty()) { |
199 | const QString kresCfgFile = KStandardDirs::locateLocal("config" , QString::fromLatin1("kresources/%1/stdrc" ).arg(kresType)); |
200 | KConfig resCfg(kresCfgFile); |
201 | const KConfigGroup resGroup(&resCfg, "General" ); |
202 | bool legacyResourceFound = false; |
203 | const QStringList kresResources = resGroup.readEntry("ResourceKeys" , QStringList()) |
204 | + resGroup.readEntry("PassiveResourceKeys" , QStringList()); |
205 | foreach (const QString &kresResource, kresResources) { |
206 | const KConfigGroup cfg(&resCfg, QString::fromLatin1("Resource_%1" ).arg(kresResource)); |
207 | if (cfg.readEntry("ResourceType" , QString()) != QLatin1String("akonadi" )) { // not a bridge |
208 | legacyResourceFound = true; |
209 | break; |
210 | } |
211 | } |
212 | if (legacyResourceFound) { |
213 | kDebug() << "ignoring " << mCurrentDefault->name() << " as there is a KResource setup for its type already." ; |
214 | KConfigGroup cfg(mConfig, "ProcessedDefaults" ); |
215 | cfg.writeEntry(agentCfg.readEntry("Id" , QString()), QString::fromLatin1("kres" )); |
216 | cfg.sync(); |
217 | setupNext(); |
218 | return; |
219 | } |
220 | } |
221 | #endif |
222 | |
223 | AgentInstanceCreateJob *job = new AgentInstanceCreateJob(type); |
224 | connect(job, SIGNAL(result(KJob*)), SLOT(instanceCreated(KJob*))); |
225 | job->start(); |
226 | } |
227 | |
228 | void Firstrun::instanceCreated(KJob *job) |
229 | { |
230 | Q_ASSERT(mCurrentDefault); |
231 | |
232 | if (job->error()) { |
233 | kError() << "Creating agent instance failed for " << mCurrentDefault->name(); |
234 | setupNext(); |
235 | return; |
236 | } |
237 | |
238 | AgentInstance instance = static_cast<AgentInstanceCreateJob *>(job)->instance(); |
239 | const KConfigGroup agentCfg = KConfigGroup(mCurrentDefault, "Agent" ); |
240 | const QString agentName = agentCfg.readEntry("Name" , QString()); |
241 | if (!agentName.isEmpty()) { |
242 | instance.setName(agentName); |
243 | } |
244 | |
245 | // agent specific settings, using the D-Bus <-> KConfigXT bridge |
246 | const KConfigGroup settings = KConfigGroup(mCurrentDefault, "Settings" ); |
247 | |
248 | QDBusInterface *iface = new QDBusInterface(QString::fromLatin1("org.freedesktop.Akonadi.Agent.%1" ).arg(instance.identifier()), |
249 | QLatin1String("/Settings" ), QString(), |
250 | DBusConnectionPool::threadConnection(), this); |
251 | if (!iface->isValid()) { |
252 | kError() << "Unable to obtain the KConfigXT D-Bus interface of " << instance.identifier(); |
253 | setupNext(); |
254 | delete iface; |
255 | return; |
256 | } |
257 | |
258 | foreach (const QString &setting, settings.keyList()) { |
259 | kDebug() << "Setting up " << setting << " for agent " << instance.identifier(); |
260 | const QString methodName = QString::fromLatin1("set%1" ).arg(setting); |
261 | const QVariant::Type argType = argumentType(iface->metaObject(), methodName); |
262 | if (argType == QVariant::Invalid) { |
263 | kError() << "Setting " << setting << " not found in agent configuration interface of " << instance.identifier(); |
264 | continue; |
265 | } |
266 | |
267 | QVariant arg; |
268 | if (argType == QVariant::String) { |
269 | // Since a string could be a path we always use readPathEntry here, |
270 | // that shouldn't harm any normal string settings |
271 | arg = settings.readPathEntry(setting, QString()); |
272 | } else { |
273 | arg = settings.readEntry(setting, QVariant(argType)); |
274 | } |
275 | |
276 | const QDBusReply<void> reply = iface->call(methodName, arg); |
277 | if (!reply.isValid()) { |
278 | kError() << "Setting " << setting << " failed for agent " << instance.identifier(); |
279 | } |
280 | } |
281 | |
282 | iface->call(QLatin1String("writeConfig" )); |
283 | |
284 | instance.reconfigure(); |
285 | instance.synchronize(); |
286 | delete iface; |
287 | |
288 | // remember we set this one up already |
289 | KConfigGroup cfg(mConfig, "ProcessedDefaults" ); |
290 | cfg.writeEntry(agentCfg.readEntry("Id" , QString()), instance.identifier()); |
291 | cfg.sync(); |
292 | |
293 | setupNext(); |
294 | } |
295 | |
296 | QVariant::Type Firstrun::argumentType(const QMetaObject *mo, const QString &method) |
297 | { |
298 | QMetaMethod m; |
299 | for (int i = 0; i < mo->methodCount(); ++i) { |
300 | const QString signature = QString::fromLatin1(mo->method(i).signature()); |
301 | if (signature.startsWith(method)) { |
302 | m = mo->method(i); |
303 | } |
304 | } |
305 | |
306 | if (!m.signature()) { |
307 | return QVariant::Invalid; |
308 | } |
309 | |
310 | const QList<QByteArray> argTypes = m.parameterTypes(); |
311 | if (argTypes.count() != 1) { |
312 | return QVariant::Invalid; |
313 | } |
314 | |
315 | return QVariant::nameToType(argTypes.first()); |
316 | } |
317 | |
318 | #include "moc_firstrun_p.cpp" |
319 | |