1 | /*************************************************************************** |
2 | * Copyright (C) 2007 by Kevin Krammer <kevin.krammer@gmx.at> * |
3 | * * |
4 | * This program is free software; you can redistribute it and/or modify * |
5 | * it under the terms of the GNU Library General Public License as * |
6 | * published by the Free Software Foundation; either version 2 of the * |
7 | * License, or (at your option) any later version. * |
8 | * * |
9 | * This program is distributed in the hope that it will be useful, * |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * |
12 | * GNU General Public License for more details. * |
13 | * * |
14 | * You should have received a copy of the GNU Library General Public * |
15 | * License along with this program; if not, write to the * |
16 | * Free Software Foundation, Inc., * |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * |
18 | ***************************************************************************/ |
19 | |
20 | #include "xdgbasedirs_p.h" |
21 | |
22 | #include "akonadi-prefix.h" // for prefix defines |
23 | |
24 | #include <QtCore/QCoreApplication> |
25 | #include <QtCore/QDebug> |
26 | #include <QtCore/QDir> |
27 | #include <QtCore/QFileInfo> |
28 | #include <QtCore/QProcess> |
29 | #include <QtCore/QSettings> |
30 | |
31 | #include <cstdlib> |
32 | |
33 | static QStringList alternateExecPaths(const QString &path) |
34 | { |
35 | QStringList pathList; |
36 | |
37 | pathList << path; |
38 | |
39 | #if defined(Q_OS_WIN) //krazy:exclude=cpp |
40 | pathList << path + QLatin1String(".exe" ); |
41 | #elif defined(Q_OS_MAC) //krazy:exclude=cpp |
42 | pathList << path + QLatin1String(".app/Contents/MacOS/" ) + path.section(QLatin1Char('/'), -1); |
43 | #endif |
44 | |
45 | return pathList; |
46 | } |
47 | |
48 | static QStringList splitPathList(const QString &pathList) |
49 | { |
50 | #if defined(Q_OS_WIN) //krazy:exclude=cpp |
51 | return pathList.split(QLatin1Char(';')); |
52 | #else |
53 | return pathList.split(QLatin1Char(':')); |
54 | #endif |
55 | } |
56 | |
57 | namespace Akonadi { |
58 | |
59 | class XdgBaseDirsPrivate |
60 | { |
61 | public: |
62 | XdgBaseDirsPrivate() |
63 | { |
64 | } |
65 | |
66 | ~XdgBaseDirsPrivate() |
67 | { |
68 | } |
69 | }; |
70 | |
71 | class |
72 | { |
73 | public: |
74 | QString homePath(const char *variable, const char *defaultSubDir); |
75 | |
76 | QStringList systemPathList(const char *variable, const char *defaultDirList); |
77 | |
78 | public: |
79 | QString ; |
80 | QString ; |
81 | |
82 | QStringList ; |
83 | QStringList ; |
84 | QStringList ; |
85 | QStringList ; |
86 | }; |
87 | |
88 | Q_GLOBAL_STATIC(XdgBaseDirsSingleton, instance) |
89 | |
90 | } |
91 | |
92 | using namespace Akonadi; |
93 | |
94 | XdgBaseDirs::XdgBaseDirs() |
95 | : d(new XdgBaseDirsPrivate()) |
96 | { |
97 | } |
98 | |
99 | XdgBaseDirs::~XdgBaseDirs() |
100 | { |
101 | delete d; |
102 | } |
103 | |
104 | QString XdgBaseDirs::homePath(const char *resource) |
105 | { |
106 | if (qstrncmp("data" , resource, 4) == 0) { |
107 | if (instance()->mDataHome.isEmpty()) { |
108 | instance()->mDataHome = instance()->homePath("XDG_DATA_HOME" , ".local/share" ); |
109 | } |
110 | return instance()->mDataHome; |
111 | } else if (qstrncmp("config" , resource, 6) == 0) { |
112 | if (instance()->mConfigHome.isEmpty()) { |
113 | instance()->mConfigHome = instance()->homePath("XDG_CONFIG_HOME" , ".config" ); |
114 | } |
115 | return instance()->mConfigHome; |
116 | } |
117 | |
118 | return QString(); |
119 | } |
120 | |
121 | QStringList XdgBaseDirs::systemPathList(const char *resource) |
122 | { |
123 | if (qstrncmp("data" , resource, 4) == 0) { |
124 | if (instance()->mDataDirs.isEmpty()) { |
125 | #ifdef Q_OS_WIN |
126 | QDir dir(QCoreApplication::applicationDirPath()); |
127 | dir.cdUp(); |
128 | const QString defaultPathList = dir.absoluteFilePath(QLatin1String("share" )); |
129 | QStringList dataDirs = instance()->systemPathList("XDG_DATA_DIRS" , defaultPathList.toLocal8Bit().constData()); |
130 | #else |
131 | QStringList dataDirs = instance()->systemPathList("XDG_DATA_DIRS" , "/usr/local/share:/usr/share" ); |
132 | #endif |
133 | |
134 | #ifdef Q_OS_WIN |
135 | const QString prefixDataDir = QLatin1String(AKONADIPREFIX "/" AKONADIDATA); |
136 | #else |
137 | const QString prefixDataDir = QLatin1String(AKONADIDATA); |
138 | #endif |
139 | if (!dataDirs.contains(prefixDataDir)) { |
140 | dataDirs << prefixDataDir; |
141 | } |
142 | |
143 | #if QT_VERSION < 0x050000 |
144 | // fallback for users with KDE in a different prefix and not correctly set up XDG_DATA_DIRS, hi David ;-) |
145 | QProcess proc; |
146 | // ### should probably rather be --path xdg-something |
147 | const QStringList args = QStringList() << QLatin1String("--prefix" ); |
148 | proc.start(QLatin1String("kde4-config" ), args); |
149 | if (proc.waitForStarted() && proc.waitForFinished() && proc.exitCode() == 0) { |
150 | proc.setReadChannel(QProcess::StandardOutput); |
151 | Q_FOREACH (const QString &basePath, splitPathList(QString::fromLocal8Bit(proc.readLine().trimmed()))) { |
152 | const QString path = basePath + QDir::separator() + QLatin1String("share" ); |
153 | if (!dataDirs.contains(path)) { |
154 | dataDirs << path; |
155 | } |
156 | } |
157 | } |
158 | #endif |
159 | |
160 | instance()->mDataDirs = dataDirs; |
161 | } |
162 | #ifdef Q_OS_WIN |
163 | QStringList dataDirs = instance()->mDataDirs; |
164 | // on Windows installation might be scattered across several directories |
165 | // so check if any installer providing agents has registered its base path |
166 | QSettings agentProviders(QSettings::SystemScope, QLatin1String("Akonadi" ), QLatin1String("Akonadi" )); |
167 | agentProviders.beginGroup(QLatin1String("AgentProviders" )); |
168 | Q_FOREACH (const QString &agentProvider, agentProviders.childKeys()) { |
169 | const QString basePath = agentProviders.value(agentProvider).toString(); |
170 | if (!basePath.isEmpty()) { |
171 | const QString path = basePath + QDir::separator() + QLatin1String("share" ); |
172 | if (!dataDirs.contains(path)) { |
173 | dataDirs << path; |
174 | } |
175 | } |
176 | } |
177 | |
178 | return dataDirs; |
179 | #else |
180 | return instance()->mDataDirs; |
181 | #endif |
182 | } else if (qstrncmp("config" , resource, 6) == 0) { |
183 | if (instance()->mConfigDirs.isEmpty()) { |
184 | #ifdef Q_OS_WIN |
185 | QDir dir(QCoreApplication::applicationDirPath()); |
186 | dir.cdUp(); |
187 | const QString defaultPathList = dir.absoluteFilePath(QLatin1String("etc" )) + QLatin1Char(';') + dir.absoluteFilePath(QLatin1String("share/config" )); |
188 | QStringList configDirs = instance()->systemPathList("XDG_CONFIG_DIRS" , defaultPathList.toLocal8Bit().constData()); |
189 | #else |
190 | QStringList configDirs = instance()->systemPathList("XDG_CONFIG_DIRS" , "/etc/xdg" ); |
191 | #endif |
192 | |
193 | #ifdef Q_OS_WIN |
194 | const QString prefixConfigDir = QLatin1String(AKONADIPREFIX "/" AKONADICONFIG); |
195 | #else |
196 | const QString prefixConfigDir = QLatin1String(AKONADICONFIG); |
197 | #endif |
198 | if (!configDirs.contains(prefixConfigDir)) { |
199 | configDirs << prefixConfigDir; |
200 | } |
201 | |
202 | instance()->mConfigDirs = configDirs; |
203 | } |
204 | return instance()->mConfigDirs; |
205 | } |
206 | |
207 | return QStringList(); |
208 | } |
209 | |
210 | QString XdgBaseDirs::findResourceFile(const char *resource, const QString &relPath) |
211 | { |
212 | const QString fullPath = homePath(resource) + QLatin1Char('/') + relPath; |
213 | |
214 | QFileInfo fileInfo(fullPath); |
215 | if (fileInfo.exists() && fileInfo.isFile() && fileInfo.isReadable()) { |
216 | return fullPath; |
217 | } |
218 | |
219 | const QStringList pathList = systemPathList(resource); |
220 | |
221 | Q_FOREACH (const QString &path, pathList) { |
222 | fileInfo = QFileInfo(path + QLatin1Char('/') + relPath); |
223 | if (fileInfo.exists() && fileInfo.isFile() && fileInfo.isReadable()) { |
224 | return fileInfo.absoluteFilePath(); |
225 | } |
226 | } |
227 | |
228 | return QString(); |
229 | } |
230 | |
231 | QString XdgBaseDirs::findExecutableFile(const QString &relPath, const QStringList &searchPath) |
232 | { |
233 | if (instance()->mExecutableDirs.isEmpty()) { |
234 | QStringList executableDirs = instance()->systemPathList("PATH" , "/usr/local/bin:/usr/bin" ); |
235 | |
236 | const QString prefixExecutableDir = QLatin1String(AKONADIPREFIX "/bin" ); |
237 | if (!executableDirs.contains(prefixExecutableDir)) { |
238 | executableDirs << prefixExecutableDir; |
239 | } |
240 | |
241 | if (QCoreApplication::instance() != 0) { |
242 | const QString appExecutableDir = QCoreApplication::instance()->applicationDirPath(); |
243 | if (!executableDirs.contains(appExecutableDir)) { |
244 | executableDirs << appExecutableDir; |
245 | } |
246 | } |
247 | |
248 | executableDirs += searchPath; |
249 | |
250 | #if defined(Q_OS_MAC) //krazy:exclude=cpp |
251 | executableDirs += QLatin1String(AKONADIBUNDLEPATH); |
252 | #endif |
253 | qWarning() << "search paths: " << executableDirs; |
254 | |
255 | instance()->mExecutableDirs = executableDirs; |
256 | } |
257 | |
258 | #ifdef Q_OS_WIN |
259 | QStringList executableDirs = instance()->mExecutableDirs; |
260 | // on Windows installation might be scattered across several directories |
261 | // so check if any installer providing agents has registered its base path |
262 | QSettings agentProviders(QSettings::SystemScope, QLatin1String("Akonadi" ), QLatin1String("Akonadi" )); |
263 | agentProviders.beginGroup(QLatin1String("AgentProviders" )); |
264 | Q_FOREACH (const QString &agentProvider, agentProviders.childKeys()) { |
265 | const QString basePath = agentProviders.value(agentProvider).toString(); |
266 | if (!basePath.isEmpty()) { |
267 | const QString path = basePath + QDir::separator() + QLatin1String("bin" ); |
268 | if (!executableDirs.contains(path)) { |
269 | executableDirs << path; |
270 | } |
271 | } |
272 | } |
273 | |
274 | QStringList::const_iterator pathIt = executableDirs.constBegin(); |
275 | const QStringList::const_iterator pathEndIt = executableDirs.constEnd(); |
276 | #else |
277 | QStringList::const_iterator pathIt = instance()->mExecutableDirs.constBegin(); |
278 | const QStringList::const_iterator pathEndIt = instance()->mExecutableDirs.constEnd(); |
279 | #endif |
280 | for (; pathIt != pathEndIt; ++pathIt) { |
281 | const QStringList fullPathList = alternateExecPaths(*pathIt + QLatin1Char('/') + relPath); |
282 | |
283 | QStringList::const_iterator it = fullPathList.constBegin(); |
284 | const QStringList::const_iterator endIt = fullPathList.constEnd(); |
285 | for (; it != endIt; ++it) { |
286 | const QFileInfo fileInfo(*it); |
287 | |
288 | // resolve symlinks, happens eg. with Maemo optify |
289 | if (fileInfo.canonicalFilePath().isEmpty()) { |
290 | continue; |
291 | } |
292 | |
293 | const QFileInfo canonicalFileInfo(fileInfo.canonicalFilePath()); |
294 | |
295 | if (canonicalFileInfo.exists() && canonicalFileInfo.isFile() && canonicalFileInfo.isExecutable()) { |
296 | return *it; |
297 | } |
298 | } |
299 | } |
300 | |
301 | return QString(); |
302 | } |
303 | |
304 | QStringList XdgBaseDirs::findPluginDirs() |
305 | { |
306 | if (instance()->mPluginDirs.isEmpty()) { |
307 | #if QT_VERSION < 0x050000 |
308 | QStringList pluginDirs = instance()->systemPathList("QT_PLUGIN_PATH" , AKONADILIB ":" AKONADILIB "/qt4/plugins/:" AKONADILIB "/kde4/:" AKONADILIB "/kde4/plugins/:/usr/lib/qt4/plugins/" ); |
309 | #else |
310 | QStringList pluginDirs = instance()->systemPathList("QT_PLUGIN_PATH" , AKONADILIB ":" AKONADILIB "/qt5/plugins/:" AKONADILIB "/kf5/:" AKONADILIB "/kf5/plugins/:/usr/lib/qt5/plugins/" ); |
311 | #endif |
312 | if (QCoreApplication::instance() != 0) { |
313 | Q_FOREACH (const QString &libraryPath, QCoreApplication::instance()->libraryPaths()) { |
314 | if (!pluginDirs.contains(libraryPath)) { |
315 | pluginDirs << libraryPath; |
316 | } |
317 | } |
318 | } |
319 | #if QT_VERSION < 0x050000 |
320 | // fallback for users with KDE in a different prefix and not correctly set up XDG_DATA_DIRS, hi David ;-) |
321 | QProcess proc; |
322 | // ### should probably rather be --path xdg-something |
323 | const QStringList args = QStringList() << QLatin1String("--path" ) << QLatin1String("module" ); |
324 | proc.start(QLatin1String("kde4-config" ), args); |
325 | if (proc.waitForStarted() && proc.waitForFinished() && proc.exitCode() == 0) { |
326 | proc.setReadChannel(QProcess::StandardOutput); |
327 | Q_FOREACH (const QString &path, splitPathList(QString::fromLocal8Bit(proc.readLine().trimmed()))) { |
328 | if (!pluginDirs.contains(path)) { |
329 | pluginDirs.append(path); |
330 | } |
331 | } |
332 | } |
333 | #endif |
334 | qWarning() << "search paths: " << pluginDirs; |
335 | instance()->mPluginDirs = pluginDirs; |
336 | } |
337 | |
338 | return instance()->mPluginDirs; |
339 | } |
340 | |
341 | QString XdgBaseDirs::findPluginFile(const QString &relPath, const QStringList &searchPath) |
342 | { |
343 | const QStringList searchDirs = findPluginDirs() + searchPath; |
344 | |
345 | #if defined(Q_OS_WIN) //krazy:exclude=cpp |
346 | const QString pluginName = relPath + QLatin1String(".dll" ); |
347 | #else |
348 | const QString pluginName = relPath + QLatin1String(".so" ); |
349 | #endif |
350 | |
351 | Q_FOREACH (const QString &path, searchDirs) { |
352 | const QFileInfo fileInfo(path + QDir::separator() + pluginName); |
353 | |
354 | // resolve symlinks, happens eg. with Maemo optify |
355 | if (fileInfo.canonicalFilePath().isEmpty()) { |
356 | continue; |
357 | } |
358 | |
359 | const QFileInfo canonicalFileInfo(fileInfo.canonicalFilePath()); |
360 | if (canonicalFileInfo.exists() && canonicalFileInfo.isFile()) { |
361 | return canonicalFileInfo.absoluteFilePath(); |
362 | } |
363 | } |
364 | |
365 | return QString(); |
366 | } |
367 | |
368 | QString XdgBaseDirs::findResourceDir(const char *resource, const QString &relPath) |
369 | { |
370 | QString fullPath = homePath(resource) + QLatin1Char('/') + relPath; |
371 | |
372 | QFileInfo fileInfo(fullPath); |
373 | if (fileInfo.exists() && fileInfo.isDir() && fileInfo.isReadable()) { |
374 | return fullPath; |
375 | } |
376 | |
377 | Q_FOREACH (const QString &path, systemPathList(resource)) { |
378 | fileInfo = QFileInfo(path + QLatin1Char('/') + relPath); |
379 | if (fileInfo.exists() && fileInfo.isDir() && fileInfo.isReadable()) { |
380 | return fileInfo.absoluteFilePath(); |
381 | } |
382 | } |
383 | |
384 | return QString(); |
385 | } |
386 | |
387 | QStringList XdgBaseDirs::findAllResourceDirs(const char *resource, const QString &relPath) |
388 | { |
389 | QStringList resultList; |
390 | |
391 | const QString fullPath = homePath(resource) + QLatin1Char('/') + relPath; |
392 | |
393 | QFileInfo fileInfo(fullPath); |
394 | if (fileInfo.exists() && fileInfo.isDir() && fileInfo.isReadable()) { |
395 | resultList << fileInfo.absoluteFilePath(); |
396 | } |
397 | |
398 | Q_FOREACH (const QString &path, systemPathList(resource)) { |
399 | fileInfo = QFileInfo(path + QLatin1Char('/') + relPath); |
400 | if (fileInfo.exists() && fileInfo.isDir() && fileInfo.isReadable()) { |
401 | const QString absPath = fileInfo.absoluteFilePath(); |
402 | if (!resultList.contains(absPath)) { |
403 | resultList << absPath; |
404 | } |
405 | } |
406 | } |
407 | |
408 | return resultList; |
409 | } |
410 | |
411 | QString XdgBaseDirs::saveDir(const char *resource, const QString &relPath) |
412 | { |
413 | const QString fullPath = homePath(resource) + QLatin1Char('/') + relPath; |
414 | |
415 | QFileInfo fileInfo(fullPath); |
416 | if (fileInfo.exists()) { |
417 | if (fileInfo.isDir()) { |
418 | return fullPath; |
419 | } else { |
420 | qWarning() << "XdgBaseDirs::saveDir: '" << fileInfo.absoluteFilePath() |
421 | << "' exists but is not a directory" ; |
422 | } |
423 | } else { |
424 | if (!QDir::home().mkpath(fileInfo.absoluteFilePath())) { |
425 | qWarning() << "XdgBaseDirs::saveDir: failed to create directory '" |
426 | << fileInfo.absoluteFilePath() << "'" ; |
427 | } else { |
428 | return fullPath; |
429 | } |
430 | } |
431 | |
432 | return QString(); |
433 | } |
434 | |
435 | QString XdgBaseDirs::akonadiServerConfigFile(FileAccessMode openMode) |
436 | { |
437 | return akonadiConfigFile(QLatin1String("akonadiserverrc" ), openMode); |
438 | } |
439 | |
440 | QString XdgBaseDirs::akonadiConnectionConfigFile(FileAccessMode openMode) |
441 | { |
442 | return akonadiConfigFile(QLatin1String("akonadiconnectionrc" ), openMode); |
443 | } |
444 | |
445 | QString XdgBaseDirs::akonadiConfigFile(const QString &file, FileAccessMode openMode) |
446 | { |
447 | const QString akonadiDir = QLatin1String("akonadi" ); |
448 | |
449 | const QString savePath = saveDir("config" , akonadiDir) + QLatin1Char('/') + file; |
450 | |
451 | if (openMode == WriteOnly) { |
452 | return savePath; |
453 | } |
454 | |
455 | const QString path = findResourceFile("config" , akonadiDir + QLatin1Char('/') + file); |
456 | |
457 | if (path.isEmpty()) { |
458 | return savePath; |
459 | } else if (openMode == ReadOnly || path == savePath) { |
460 | return path; |
461 | } |
462 | |
463 | // file found in system paths and mode is ReadWrite, thus |
464 | // we copy to the home path location and return this path |
465 | QFile systemFile(path); |
466 | |
467 | systemFile.copy(savePath); |
468 | |
469 | return savePath; |
470 | } |
471 | |
472 | QString XdgBaseDirsSingleton::(const char *variable, const char *defaultSubDir) |
473 | { |
474 | const QByteArray env = qgetenv(variable); |
475 | |
476 | QString xdgPath; |
477 | if (env.isEmpty()) { |
478 | xdgPath = QDir::homePath() + QLatin1Char('/') + QLatin1String(defaultSubDir); |
479 | #if defined(Q_OS_WIN) //krazy:exclude=cpp |
480 | } else if (QDir::isAbsolutePath(QString::fromLocal8Bit(env))) { |
481 | #else |
482 | } else if (env.startsWith('/')) { |
483 | #endif |
484 | xdgPath = QString::fromLocal8Bit(env); |
485 | } else { |
486 | xdgPath = QDir::homePath() + QLatin1Char('/') + QString::fromLocal8Bit(env); |
487 | } |
488 | |
489 | return xdgPath; |
490 | } |
491 | |
492 | QStringList XdgBaseDirsSingleton::(const char *variable, const char *defaultDirList) |
493 | { |
494 | const QByteArray env = qgetenv(variable); |
495 | |
496 | QString xdgDirList; |
497 | if (env.isEmpty()) { |
498 | xdgDirList = QLatin1String(defaultDirList); |
499 | } else { |
500 | xdgDirList = QString::fromLocal8Bit(env); |
501 | } |
502 | |
503 | return splitPathList(xdgDirList); |
504 | } |
505 | |