kde-playground/akonadi/libs/xdgbasedirs.cpp
2015-09-23 09:37:02 +00:00

501 lines
16 KiB
C++

/***************************************************************************
* Copyright (C) 2007 by Kevin Krammer <kevin.krammer@gmx.at> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Library General Public License as *
* published by the Free Software Foundation; either version 2 of the *
* License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
#include "xdgbasedirs_p.h"
#include "akonadi-prefix.h" // for prefix defines
#include <QtCore/QCoreApplication>
#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
#include <QtCore/QProcess>
#include <QtCore/QSettings>
#include <cstdlib>
static QStringList alternateExecPaths( const QString &path )
{
QStringList pathList;
pathList << path;
#if defined(Q_OS_WIN) //krazy:exclude=cpp
pathList << path + QLatin1String( ".exe" );
#elif defined(Q_OS_MAC) //krazy:exclude=cpp
pathList << path + QLatin1String( ".app/Contents/MacOS/" ) + path.section( QLatin1Char( '/' ), -1 );
#endif
return pathList;
}
static QStringList splitPathList( const QString &pathList )
{
#if defined(Q_OS_WIN) //krazy:exclude=cpp
return pathList.split( QLatin1Char( ';' ) );
#else
return pathList.split( QLatin1Char( ':' ) );
#endif
}
namespace Akonadi {
class XdgBaseDirsPrivate
{
public:
XdgBaseDirsPrivate()
{
}
~XdgBaseDirsPrivate()
{
}
};
class XdgBaseDirsSingleton
{
public:
QString homePath( const char *variable, const char *defaultSubDir );
QStringList systemPathList( const char *variable, const char *defaultDirList );
public:
QString mConfigHome;
QString mDataHome;
QStringList mConfigDirs;
QStringList mDataDirs;
QStringList mExecutableDirs;
QStringList mPluginDirs;
};
Q_GLOBAL_STATIC( XdgBaseDirsSingleton, instance )
}
using namespace Akonadi;
XdgBaseDirs::XdgBaseDirs()
: d( new XdgBaseDirsPrivate() )
{
}
XdgBaseDirs::~XdgBaseDirs()
{
delete d;
}
QString XdgBaseDirs::homePath( const char *resource )
{
if ( qstrncmp( "data", resource, 4 ) == 0 ) {
if ( instance()->mDataHome.isEmpty() ) {
instance()->mDataHome = instance()->homePath( "XDG_DATA_HOME", ".local/share" );
}
return instance()->mDataHome;
} else if ( qstrncmp( "config", resource, 6 ) == 0 ) {
if ( instance()->mConfigHome.isEmpty() ) {
instance()->mConfigHome = instance()->homePath( "XDG_CONFIG_HOME", ".config" );
}
return instance()->mConfigHome;
}
return QString();
}
QStringList XdgBaseDirs::systemPathList( const char *resource )
{
if ( qstrncmp( "data", resource, 4 ) == 0 ) {
if ( instance()->mDataDirs.isEmpty() ) {
#ifdef Q_OS_WIN
QDir dir( QCoreApplication::applicationDirPath() );
dir.cdUp();
const QString defaultPathList = dir.absoluteFilePath( QLatin1String( "share" ) );
QStringList dataDirs = instance()->systemPathList( "XDG_DATA_DIRS", defaultPathList.toLocal8Bit().constData() );
#else
QStringList dataDirs = instance()->systemPathList( "XDG_DATA_DIRS", "/usr/local/share:/usr/share" );
#endif
#ifdef Q_OS_WIN
const QString prefixDataDir = QLatin1String( AKONADIPREFIX "/" AKONADIDATA );
#else
const QString prefixDataDir = QLatin1String( AKONADIDATA );
#endif
if ( !dataDirs.contains( prefixDataDir ) ) {
dataDirs << prefixDataDir;
}
#if QT_VERSION < 0x050000
// fallback for users with KDE in a different prefix and not correctly set up XDG_DATA_DIRS, hi David ;-)
QProcess proc;
// ### should probably rather be --path xdg-something
const QStringList args = QStringList() << QLatin1String( "--prefix" );
proc.start( QLatin1String( "kde4-config" ), args );
if ( proc.waitForStarted() && proc.waitForFinished() && proc.exitCode() == 0 ) {
proc.setReadChannel( QProcess::StandardOutput );
Q_FOREACH ( const QString &basePath, splitPathList( QString::fromLocal8Bit( proc.readLine().trimmed() ) ) ) {
const QString path = basePath + QDir::separator() + QLatin1String( "share" );
if ( !dataDirs.contains( path ) ) {
dataDirs << path;
}
}
}
#endif
instance()->mDataDirs = dataDirs;
}
#ifdef Q_OS_WIN
QStringList dataDirs = instance()->mDataDirs;
// on Windows installation might be scattered across several directories
// so check if any installer providing agents has registered its base path
QSettings agentProviders( QSettings::SystemScope, QLatin1String( "Akonadi" ), QLatin1String( "Akonadi" ) );
agentProviders.beginGroup( QLatin1String( "AgentProviders" ) );
Q_FOREACH ( const QString &agentProvider, agentProviders.childKeys() ) {
const QString basePath = agentProviders.value( agentProvider ).toString();
if ( !basePath.isEmpty() ) {
const QString path = basePath + QDir::separator() + QLatin1String( "share" );
if ( !dataDirs.contains( path ) ) {
dataDirs << path;
}
}
}
return dataDirs;
#else
return instance()->mDataDirs;
#endif
} else if ( qstrncmp( "config", resource, 6 ) == 0 ) {
if ( instance()->mConfigDirs.isEmpty() ) {
#ifdef Q_OS_WIN
QDir dir( QCoreApplication::applicationDirPath() );
dir.cdUp();
const QString defaultPathList = dir.absoluteFilePath( QLatin1String( "etc" ) ) + QLatin1Char( ';' ) + dir.absoluteFilePath( QLatin1String( "share/config" ) );
QStringList configDirs = instance()->systemPathList( "XDG_CONFIG_DIRS", defaultPathList.toLocal8Bit().constData() );
#else
QStringList configDirs = instance()->systemPathList( "XDG_CONFIG_DIRS", "/etc/xdg" );
#endif
#ifdef Q_OS_WIN
const QString prefixConfigDir = QLatin1String( AKONADIPREFIX "/" AKONADICONFIG );
#else
const QString prefixConfigDir = QLatin1String( AKONADICONFIG );
#endif
if ( !configDirs.contains( prefixConfigDir ) ) {
configDirs << prefixConfigDir;
}
instance()->mConfigDirs = configDirs;
}
return instance()->mConfigDirs;
}
return QStringList();
}
QString XdgBaseDirs::findResourceFile( const char *resource, const QString &relPath )
{
const QString fullPath = homePath( resource ) + QLatin1Char( '/' ) + relPath;
QFileInfo fileInfo( fullPath );
if ( fileInfo.exists() && fileInfo.isFile() && fileInfo.isReadable() ) {
return fullPath;
}
const QStringList pathList = systemPathList( resource );
Q_FOREACH ( const QString &path, pathList ) {
fileInfo = QFileInfo( path + QLatin1Char( '/' ) + relPath );
if ( fileInfo.exists() && fileInfo.isFile() && fileInfo.isReadable() ) {
return fileInfo.absoluteFilePath();
}
}
return QString();
}
QString XdgBaseDirs::findExecutableFile( const QString &relPath, const QStringList &searchPath )
{
if ( instance()->mExecutableDirs.isEmpty() ) {
QStringList executableDirs = instance()->systemPathList( "PATH", "/usr/local/bin:/usr/bin" );
const QString prefixExecutableDir = QLatin1String( AKONADIPREFIX "/bin" );
if ( !executableDirs.contains( prefixExecutableDir ) ) {
executableDirs << prefixExecutableDir;
}
if ( QCoreApplication::instance() != 0 ) {
const QString appExecutableDir = QCoreApplication::instance()->applicationDirPath();
if ( !executableDirs.contains( appExecutableDir ) ) {
executableDirs << appExecutableDir;
}
}
executableDirs += searchPath;
#if defined(Q_OS_MAC) //krazy:exclude=cpp
executableDirs += QLatin1String( AKONADIBUNDLEPATH );
#endif
qWarning() << "search paths: " << executableDirs;
instance()->mExecutableDirs = executableDirs;
}
#ifdef Q_OS_WIN
QStringList executableDirs = instance()->mExecutableDirs;
// on Windows installation might be scattered across several directories
// so check if any installer providing agents has registered its base path
QSettings agentProviders( QSettings::SystemScope, QLatin1String( "Akonadi" ), QLatin1String( "Akonadi" ) );
agentProviders.beginGroup( QLatin1String( "AgentProviders" ) );
Q_FOREACH ( const QString &agentProvider, agentProviders.childKeys() ) {
const QString basePath = agentProviders.value( agentProvider ).toString();
if ( !basePath.isEmpty() ) {
const QString path = basePath + QDir::separator() + QLatin1String( "bin" );
if ( !executableDirs.contains( path ) ) {
executableDirs << path;
}
}
}
QStringList::const_iterator pathIt = executableDirs.constBegin();
const QStringList::const_iterator pathEndIt = executableDirs.constEnd();
#else
QStringList::const_iterator pathIt = instance()->mExecutableDirs.constBegin();
const QStringList::const_iterator pathEndIt = instance()->mExecutableDirs.constEnd();
#endif
for ( ; pathIt != pathEndIt; ++pathIt ) {
const QStringList fullPathList = alternateExecPaths( *pathIt + QLatin1Char( '/' ) + relPath );
QStringList::const_iterator it = fullPathList.constBegin();
const QStringList::const_iterator endIt = fullPathList.constEnd();
for ( ; it != endIt; ++it ) {
const QFileInfo fileInfo( *it );
// resolve symlinks, happens eg. with Maemo optify
if ( fileInfo.canonicalFilePath().isEmpty() ) {
continue;
}
const QFileInfo canonicalFileInfo( fileInfo.canonicalFilePath() );
if ( canonicalFileInfo.exists() && canonicalFileInfo.isFile() && canonicalFileInfo.isExecutable() ) {
return *it;
}
}
}
return QString();
}
QStringList XdgBaseDirs::findPluginDirs()
{
if ( instance()->mPluginDirs.isEmpty() ) {
QStringList pluginDirs = instance()->systemPathList( "QT_PLUGIN_PATH", AKONADILIB ":" AKONADILIB "/qt4/plugins/:" AKONADILIB "/kde4/:" AKONADILIB "/kde4/plugins/:/usr/lib/qt4/plugins/" );
if ( QCoreApplication::instance() != 0 ) {
Q_FOREACH ( const QString &libraryPath, QCoreApplication::instance()->libraryPaths() ) {
if ( !pluginDirs.contains( libraryPath ) ) {
pluginDirs << libraryPath;
}
}
}
// fallback for users with KDE in a different prefix and not correctly set up XDG_DATA_DIRS, hi David ;-)
QProcess proc;
// ### should probably rather be --path xdg-something
const QStringList args = QStringList() << QLatin1String( "--path" ) << QLatin1String( "module" );
proc.start( QLatin1String( "kde4-config" ), args );
if ( proc.waitForStarted() && proc.waitForFinished() && proc.exitCode() == 0 ) {
proc.setReadChannel( QProcess::StandardOutput );
Q_FOREACH ( const QString &path, splitPathList( QString::fromLocal8Bit( proc.readLine().trimmed() ) ) ) {
if ( !pluginDirs.contains( path ) ) {
pluginDirs.append( path );
}
}
}
qWarning() << "search paths: " << pluginDirs;
instance()->mPluginDirs = pluginDirs;
}
return instance()->mPluginDirs;
}
QString XdgBaseDirs::findPluginFile( const QString &relPath, const QStringList &searchPath )
{
const QStringList searchDirs = findPluginDirs() + searchPath;
#if defined(Q_OS_WIN) //krazy:exclude=cpp
const QString pluginName = relPath + QLatin1String( ".dll" );
#else
const QString pluginName = relPath + QLatin1String( ".so" );
#endif
Q_FOREACH ( const QString &path, searchDirs ) {
const QFileInfo fileInfo( path + QDir::separator() + pluginName );
// resolve symlinks, happens eg. with Maemo optify
if ( fileInfo.canonicalFilePath().isEmpty() ) {
continue;
}
const QFileInfo canonicalFileInfo( fileInfo.canonicalFilePath() );
if ( canonicalFileInfo.exists() && canonicalFileInfo.isFile() ) {
return canonicalFileInfo.absoluteFilePath();
}
}
return QString();
}
QString XdgBaseDirs::findResourceDir( const char *resource, const QString &relPath )
{
QString fullPath = homePath( resource ) + QLatin1Char( '/' ) + relPath;
QFileInfo fileInfo( fullPath );
if ( fileInfo.exists() && fileInfo.isDir() && fileInfo.isReadable() ) {
return fullPath;
}
Q_FOREACH ( const QString &path, systemPathList( resource ) ) {
fileInfo = QFileInfo( path + QLatin1Char( '/' ) + relPath );
if ( fileInfo.exists() && fileInfo.isDir() && fileInfo.isReadable() ) {
return fileInfo.absoluteFilePath();
}
}
return QString();
}
QStringList XdgBaseDirs::findAllResourceDirs( const char *resource, const QString &relPath )
{
QStringList resultList;
const QString fullPath = homePath( resource ) + QLatin1Char( '/' ) + relPath;
QFileInfo fileInfo( fullPath );
if ( fileInfo.exists() && fileInfo.isDir() && fileInfo.isReadable() ) {
resultList << fileInfo.absoluteFilePath();
}
Q_FOREACH ( const QString &path, systemPathList( resource ) ) {
fileInfo = QFileInfo( path + QLatin1Char( '/' ) + relPath );
if ( fileInfo.exists() && fileInfo.isDir() && fileInfo.isReadable() ) {
const QString absPath = fileInfo.absoluteFilePath();
if ( !resultList.contains( absPath ) ) {
resultList << absPath;
}
}
}
return resultList;
}
QString XdgBaseDirs::saveDir( const char *resource, const QString &relPath )
{
const QString fullPath = homePath( resource ) + QLatin1Char( '/' ) + relPath;
QFileInfo fileInfo( fullPath );
if ( fileInfo.exists() ) {
if ( fileInfo.isDir() ) {
return fullPath;
} else {
qWarning() << "XdgBaseDirs::saveDir: '" << fileInfo.absoluteFilePath()
<< "' exists but is not a directory";
}
} else {
if ( !QDir::home().mkpath( fileInfo.absoluteFilePath() ) ) {
qWarning() << "XdgBaseDirs::saveDir: failed to create directory '"
<< fileInfo.absoluteFilePath() << "'";
} else {
return fullPath;
}
}
return QString();
}
QString XdgBaseDirs::akonadiServerConfigFile( FileAccessMode openMode )
{
return akonadiConfigFile( QLatin1String( "akonadiserverrc" ), openMode );
}
QString XdgBaseDirs::akonadiConnectionConfigFile( FileAccessMode openMode )
{
return akonadiConfigFile( QLatin1String( "akonadiconnectionrc" ), openMode );
}
QString XdgBaseDirs::akonadiConfigFile( const QString &file, FileAccessMode openMode )
{
const QString akonadiDir = QLatin1String( "akonadi" );
const QString savePath = saveDir( "config", akonadiDir ) + QLatin1Char( '/' ) + file;
if ( openMode == WriteOnly ) {
return savePath;
}
const QString path = findResourceFile( "config", akonadiDir + QLatin1Char( '/' ) + file );
if ( path.isEmpty() ) {
return savePath;
} else if ( openMode == ReadOnly || path == savePath ) {
return path;
}
// file found in system paths and mode is ReadWrite, thus
// we copy to the home path location and return this path
QFile systemFile( path );
systemFile.copy( savePath );
return savePath;
}
QString XdgBaseDirsSingleton::homePath( const char *variable, const char *defaultSubDir )
{
const QByteArray env = qgetenv( variable );
QString xdgPath;
if ( env.isEmpty() ) {
xdgPath = QDir::homePath() + QLatin1Char( '/' ) + QLatin1String( defaultSubDir );
#if defined(Q_OS_WIN) //krazy:exclude=cpp
} else if ( QDir::isAbsolutePath( QString::fromLocal8Bit( env ) ) ) {
#else
} else if ( env.startsWith( '/' ) ) {
#endif
xdgPath = QString::fromLocal8Bit( env );
} else {
xdgPath = QDir::homePath() + QLatin1Char( '/' ) + QString::fromLocal8Bit( env );
}
return xdgPath;
}
QStringList XdgBaseDirsSingleton::systemPathList( const char *variable, const char *defaultDirList )
{
const QByteArray env = qgetenv( variable );
QString xdgDirList;
if ( env.isEmpty() ) {
xdgDirList = QLatin1String( defaultDirList );
} else {
xdgDirList = QString::fromLocal8Bit( env );
}
return splitPathList( xdgDirList );
}