File indexing completed on 2024-05-12 15:59:56

0001 /*
0002  * SPDX-FileCopyrightText: 2015 Boudewijn Rempt <boud@valdyas.org>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.1-or-later
0005  */
0006 #include "KoResourcePaths.h"
0007 
0008 #include <QGlobalStatic>
0009 #include <QString>
0010 #include <QStringList>
0011 #include <QMap>
0012 #include <QStandardPaths>
0013 #include <QDir>
0014 #include <QFileInfo>
0015 #include <QDebug>
0016 #include <QCoreApplication>
0017 #include <QMutex>
0018 #include "kis_debug.h"
0019 #include "ksharedconfig.h"
0020 #include "kconfiggroup.h"
0021 #include "KisResourceLocator.h"
0022 #include "KisWindowsPackageUtils.h"
0023 
0024 Q_GLOBAL_STATIC(KoResourcePaths, s_instance)
0025 
0026 QString KoResourcePaths::s_overrideAppDataLocation;
0027 
0028 namespace {
0029 
0030 static QString cleanup(const QString &path)
0031 {
0032     return QDir::cleanPath(path);
0033 }
0034 
0035 
0036 static QStringList cleanup(const QStringList &pathList)
0037 {
0038     QStringList cleanedPathList;
0039 
0040     bool getRidOfAppDataLocation = KoResourcePaths::getAppDataLocation() != QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
0041     const QString writableLocation = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
0042 
0043      Q_FOREACH(const QString &path, pathList) {
0044         QString cleanPath = cleanup(path);
0045         if (getRidOfAppDataLocation && cleanPath.startsWith(writableLocation)) {
0046             continue;
0047         }
0048         cleanedPathList << cleanPath;
0049     }
0050     return cleanedPathList;
0051 }
0052 
0053 static QStringList cleanupDirs(const QStringList &pathList)
0054 {
0055     QStringList cleanedPathList;
0056 
0057     bool getRidOfAppDataLocation = KoResourcePaths::getAppDataLocation() != QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
0058     const QString writableLocation = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
0059 
0060     Q_FOREACH(const QString &path, pathList) {
0061         QString cleanPath = QDir::cleanPath(path) + '/';
0062         if (getRidOfAppDataLocation && cleanPath.startsWith(writableLocation)) {
0063             continue;
0064         }
0065         cleanedPathList << cleanPath;
0066     }
0067     return cleanedPathList;
0068 }
0069 
0070 void appendResources(QStringList *dst, const QStringList &src, bool eliminateDuplicates)
0071 {
0072     Q_FOREACH (const QString &resource, src) {
0073         QString realPath = QDir::cleanPath(resource);
0074         if (!eliminateDuplicates || !dst->contains(realPath)) {
0075             *dst << realPath;
0076         }
0077     }
0078 }
0079 
0080 
0081 #ifdef Q_OS_WIN
0082 static const Qt::CaseSensitivity cs = Qt::CaseInsensitive;
0083 #else
0084 static const Qt::CaseSensitivity cs = Qt::CaseSensitive;
0085 #endif
0086 
0087 #ifdef Q_OS_MACOS
0088 #include <ApplicationServices/ApplicationServices.h>
0089 #include <CoreFoundation/CoreFoundation.h>
0090 #include <CoreServices/CoreServices.h>
0091 #endif
0092 
0093 QString getInstallationPrefix() {
0094 #ifdef Q_OS_MACOS
0095     QString appPath = qApp->applicationDirPath();
0096 
0097     dbgResources << "1" << appPath;
0098     appPath.chop(QString("MacOS/").length());
0099     dbgResources << "2" << appPath;
0100 
0101     bool makeInstall = QDir(appPath + "/../../../share/kritaplugins").exists();
0102     bool inBundle = QDir(appPath + "/Resources/kritaplugins").exists();
0103 
0104     QString bundlePath;
0105 
0106     if (inBundle) {
0107         bundlePath = appPath + "/";
0108     }
0109     else if (makeInstall) {
0110         appPath.chop(QString("Contents/").length());
0111         bundlePath = appPath + "/../../";
0112     }
0113     else {
0114         // This is needed as tests will not run outside of the
0115         // install directory without this
0116         // This needs krita to be installed.
0117         QString envInstallPath = qgetenv("KIS_TEST_PREFIX_PATH");
0118         if (!envInstallPath.isEmpty() && (
0119                     QDir(envInstallPath + "/share/kritaplugins").exists()
0120                     || QDir(envInstallPath + "/Resources/kritaplugins").exists() ))
0121         {
0122             bundlePath = envInstallPath;
0123         }
0124         else {
0125             qFatal("Cannot calculate the bundle path from the app path");
0126             qInfo() << "If running tests set KIS_TEST_PREFIX_PATH to krita install prefix";
0127         }
0128     }
0129 
0130     return bundlePath;
0131 #elif defined(Q_OS_ANDROID)
0132     // qApp->applicationDirPath() isn't writable and android system won't allow
0133     // any files other than libraries
0134     // NOTE the subscript [1]. It points to the internal location.
0135     return QStandardPaths::standardLocations(QStandardPaths::AppDataLocation)[1] + "/";
0136 #else
0137     return qApp->applicationDirPath() + "/../";
0138 #endif
0139 }
0140 
0141 }
0142 
0143 class Q_DECL_HIDDEN KoResourcePaths::Private {
0144 public:
0145     QMap<QString, QStringList> absolutes; // For each resource type, the list of absolute paths, from most local (most priority) to most global
0146     QMap<QString, QStringList> relatives; // Same with relative paths
0147 
0148     QMutex relativesMutex;
0149     QMutex absolutesMutex;
0150 
0151     QStringList aliases(const QString &type)
0152     {
0153         QStringList r;
0154         QStringList a;
0155         relativesMutex.lock();
0156         if (relatives.contains(type)) {
0157             r += relatives[type];
0158         }
0159         relativesMutex.unlock();
0160         absolutesMutex.lock();
0161         if (absolutes.contains(type)) {
0162             a += absolutes[type];
0163         }
0164         absolutesMutex.unlock();
0165 
0166         return r + a;
0167     }
0168 
0169     QStandardPaths::StandardLocation mapTypeToQStandardPaths(const QString &type)
0170     {
0171         if (type == "appdata") {
0172             return QStandardPaths::AppDataLocation;
0173         }
0174         else if (type == "data") {
0175             return QStandardPaths::AppDataLocation;
0176         }
0177         else if (type == "cache") {
0178             return QStandardPaths::CacheLocation;
0179         }
0180         else if (type == "locale") {
0181             return QStandardPaths::AppDataLocation;
0182         }
0183         else if (type == "genericdata") {
0184             return QStandardPaths::GenericDataLocation;
0185         }
0186         else {
0187             return QStandardPaths::AppDataLocation;
0188         }
0189     }
0190 };
0191 
0192 KoResourcePaths::KoResourcePaths()
0193     : d(new Private)
0194 {
0195 }
0196 
0197 KoResourcePaths::~KoResourcePaths()
0198 {
0199 }
0200 
0201 QString KoResourcePaths::getApplicationRoot()
0202 {
0203     return getInstallationPrefix();
0204 }
0205 
0206 QString KoResourcePaths::getAppDataLocation()
0207 {
0208     if (!s_overrideAppDataLocation.isEmpty()) {
0209         return s_overrideAppDataLocation;
0210     }
0211 
0212     QString path;
0213 
0214     KConfigGroup cfg(KSharedConfig::openConfig(), "");
0215     path = cfg.readEntry(KisResourceLocator::resourceLocationKey, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
0216 
0217     return path;
0218 
0219 }
0220 
0221 void KoResourcePaths::getAllUserResourceFoldersLocationsForWindowsStore(QString &standardLocation, QString &privateLocation)
0222 {
0223     standardLocation = "";
0224     privateLocation = "";
0225     QString resourcePath = QDir(KisResourceLocator::instance()->resourceLocationBase()).absolutePath();
0226 #ifndef Q_OS_WIN
0227     // not Windows, no problem
0228     standardLocation = resourcePath;
0229     return;
0230 #else
0231     if (!KisWindowsPackageUtils::isRunningInPackage()) {
0232         standardLocation = resourcePath; // Windows, but not Windows Store, so no problem
0233         return;
0234     }
0235 
0236     // running inside Windows Store
0237     const QDir resourceDir(resourcePath);
0238     QDir appDataGeneralDir = QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
0239     appDataGeneralDir.cdUp();
0240     const QString appDataGeneralDirPath = appDataGeneralDir.path();
0241     if (resourceDir.absolutePath().contains(appDataGeneralDirPath, Qt::CaseInsensitive)) {
0242         // resource folder location is inside appdata, so it can cause issues
0243         // from inside of Krita, we can't determine whether it uses genuine %AppData% or the private Windows Store one
0244         // so, half of the time, a custom folder inside %AppData% wouldn't work
0245         // we can't fix that, we can only inform users about it or prevent them from choosing such folder
0246         // in any case, here we need to return both folders: inside normal appdata and the private one
0247         // (note that this case also handles the default resource folder called "krita" inside the appdata)
0248 
0249 
0250         const QString folderName = QFileInfo(resourcePath).fileName();
0251 
0252         const QString privateAppData = KisWindowsPackageUtils::getPackageRoamingAppDataLocation();
0253         const QDir privateResourceDir(QDir::fromNativeSeparators(privateAppData) + '/' + folderName);
0254 
0255         standardLocation = resourcePath;
0256 
0257         if (privateResourceDir.exists()) {
0258             privateLocation = privateResourceDir.absolutePath();
0259         }
0260 
0261         return;
0262 
0263     } else {
0264         standardLocation = resourcePath; // custom folder not inside AppData, so no problem (hopefully)
0265         return;
0266     }
0267 
0268 #endif
0269 }
0270 
0271 void KoResourcePaths::addAssetType(const QString &type, const char *basetype,
0272                                       const QString &relativeName, bool priority)
0273 {
0274     s_instance->addResourceTypeInternal(type, QString::fromLatin1(basetype), relativeName, priority);
0275 }
0276 
0277 void KoResourcePaths::addAssetDir(const QString &type, const QString &dir, bool priority)
0278 {
0279     s_instance->addResourceDirInternal(type, dir, priority);
0280 }
0281 
0282 QString KoResourcePaths::findAsset(const QString &type, const QString &fileName)
0283 {
0284     return cleanup(s_instance->findResourceInternal(type, fileName));
0285 }
0286 
0287 QStringList KoResourcePaths::findDirs(const QString &type)
0288 {
0289     return cleanupDirs(s_instance->findDirsInternal(type));
0290 }
0291 
0292 QStringList KoResourcePaths::findAllAssets(const QString &type,
0293                                               const QString &filter,
0294                                               SearchOptions options)
0295 {
0296     return cleanup(s_instance->findAllResourcesInternal(type, filter, options));
0297 }
0298 
0299 QStringList KoResourcePaths::assetDirs(const QString &type)
0300 {
0301     return cleanupDirs(s_instance->resourceDirsInternal(type));
0302 }
0303 
0304 QString KoResourcePaths::saveLocation(const QString &type, const QString &suffix, bool create)
0305 {
0306     return QDir::cleanPath(s_instance->saveLocationInternal(type, suffix, create)) + '/';
0307 }
0308 
0309 QString KoResourcePaths::locate(const QString &type, const QString &filename)
0310 {
0311     return cleanup(s_instance->locateInternal(type, filename));
0312 }
0313 
0314 QString KoResourcePaths::locateLocal(const QString &type, const QString &filename, bool createDir)
0315 {
0316     return cleanup(s_instance->locateLocalInternal(type, filename, createDir));
0317 }
0318 
0319 void KoResourcePaths::addResourceTypeInternal(const QString &type, const QString &basetype,
0320                                               const QString &relativename,
0321                                               bool priority)
0322 {
0323     Q_UNUSED(basetype);
0324     if (relativename.isEmpty()) return;
0325 
0326     QString copy = relativename;
0327 
0328     Q_ASSERT(basetype == "data");
0329 
0330     if (!copy.endsWith(QLatin1Char('/'))) {
0331         copy += QLatin1Char('/');
0332     }
0333 
0334     d->relativesMutex.lock();
0335     QStringList &rels = d->relatives[type]; // find or insert
0336 
0337     if (!rels.contains(copy, cs)) {
0338         if (priority) {
0339             rels.prepend(copy);
0340         } else {
0341             rels.append(copy);
0342         }
0343     }
0344     d->relativesMutex.unlock();
0345 
0346     dbgResources << "addResourceType: type" << type << "basetype" << basetype << "relativename" << relativename << "priority" << priority << d->relatives[type];
0347 }
0348 
0349 void KoResourcePaths::addResourceDirInternal(const QString &type, const QString &absdir, bool priority)
0350 {
0351     if (absdir.isEmpty() || type.isEmpty()) return;
0352 
0353     // find or insert entry in the map
0354     QString copy = absdir;
0355     if (copy.at(copy.length() - 1) != QLatin1Char('/')) {
0356         copy += QLatin1Char('/');
0357     }
0358 
0359     d->absolutesMutex.lock();
0360     QStringList &paths = d->absolutes[type];
0361     if (!paths.contains(copy, cs)) {
0362         if (priority) {
0363             paths.prepend(copy);
0364         } else {
0365             paths.append(copy);
0366         }
0367     }
0368     d->absolutesMutex.unlock();
0369 
0370     dbgResources << "addResourceDir: type" << type << "absdir" << absdir << "priority" << priority << d->absolutes[type];
0371 }
0372 
0373 QString KoResourcePaths::findResourceInternal(const QString &type, const QString &fileName)
0374 {
0375     QStringList aliases = d->aliases(type);
0376     dbgResources<< "aliases" << aliases << getApplicationRoot();
0377     QString resource = QStandardPaths::locate(QStandardPaths::AppDataLocation, fileName, QStandardPaths::LocateFile);
0378 
0379     if (resource.isEmpty()) {
0380         Q_FOREACH (const QString &alias, aliases) {
0381             resource = QStandardPaths::locate(d->mapTypeToQStandardPaths(type), alias + '/' + fileName, QStandardPaths::LocateFile);
0382             if (QFile::exists(resource)) {
0383                 break;
0384             }
0385         }
0386     }
0387     if (resource.isEmpty() || !QFile::exists(resource)) {
0388         QString approot = getApplicationRoot();
0389         Q_FOREACH (const QString &alias, aliases) {
0390             resource = approot + "/share/" + alias + '/' + fileName;
0391             if (QFile::exists(resource)) {
0392                 break;
0393             }
0394         }
0395     }
0396     if (resource.isEmpty() || !QFile::exists(resource)) {
0397         QString approot = getApplicationRoot();
0398         Q_FOREACH (const QString &alias, aliases) {
0399             resource = approot + "/share/krita/" + alias + '/' + fileName;
0400             if (QFile::exists(resource)) {
0401                 break;
0402             }
0403         }
0404     }
0405 
0406     if (resource.isEmpty() || !QFile::exists(resource)) {
0407         QStringList extraResourceDirs = findExtraResourceDirs();
0408 
0409         if (!extraResourceDirs.isEmpty()) {
0410             Q_FOREACH(const QString &extraResourceDir, extraResourceDirs) {
0411                 if (aliases.isEmpty()) {
0412                     resource = extraResourceDir + '/' + fileName;
0413                     dbgResources<< "\t4" << resource;
0414                     if (QFile::exists(resource)) {
0415                         break;
0416                     }
0417                 }
0418                 else {
0419                     Q_FOREACH (const QString &alias, aliases) {
0420                         resource = extraResourceDir + '/' + alias + '/' + fileName;
0421                         dbgResources<< "\t4" << resource;
0422                         if (QFile::exists(resource)) {
0423                             break;
0424                         }
0425                     }
0426                 }
0427             }
0428         }
0429     }
0430 
0431     dbgResources<< "findResource: type" << type << "filename" << fileName << "resource" << resource;
0432     Q_ASSERT(!resource.isEmpty());
0433     return resource;
0434 }
0435 
0436 
0437 QStringList filesInDir(const QString &startdir, const QString & filter, bool recursive)
0438 {
0439     dbgResources << "filesInDir: startdir" << startdir << "filter" << filter << "recursive" << recursive;
0440     QStringList result;
0441 
0442     // First the entries in this path
0443     QStringList nameFilters;
0444     nameFilters << filter;
0445     const QStringList fileNames = QDir(startdir).entryList(nameFilters, QDir::Files | QDir::CaseSensitive, QDir::Name);
0446     dbgResources << "\tFound:" << fileNames.size() << ":" << fileNames;
0447     Q_FOREACH (const QString &fileName, fileNames) {
0448         QString file = startdir + '/' + fileName;
0449         result << file;
0450     }
0451 
0452     // And then everything underneath, if recursive is specified
0453     if (recursive) {
0454         const QStringList entries = QDir(startdir).entryList(QDir::Dirs | QDir::NoDotAndDotDot);
0455         Q_FOREACH (const QString &subdir, entries) {
0456             dbgResources << "\tGoing to look in subdir" << subdir << "of" << startdir;
0457             result << filesInDir(startdir + '/' + subdir, filter, recursive);
0458         }
0459     }
0460     return result;
0461 }
0462 
0463 QStringList KoResourcePaths::findDirsInternal(const QString &type)
0464 {
0465     QStringList aliases = d->aliases(type);
0466     dbgResources << type << aliases << d->mapTypeToQStandardPaths(type);
0467 
0468     QStringList dirs;
0469     QStringList standardDirs =
0470             QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), "", QStandardPaths::LocateDirectory);
0471 
0472     appendResources(&dirs, standardDirs, true);
0473 
0474     Q_FOREACH (const QString &alias, aliases) {
0475         QStringList aliasDirs =
0476                 QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), alias + '/', QStandardPaths::LocateDirectory);
0477         appendResources(&dirs, aliasDirs, true);
0478 
0479 #ifdef Q_OS_MACOS
0480         dbgResources << "MAC:" << getApplicationRoot();
0481         QStringList bundlePaths;
0482         bundlePaths << getApplicationRoot() + "/share/krita/" + alias;
0483         bundlePaths << getApplicationRoot() + "/../share/krita/" + alias;
0484         dbgResources << "bundlePaths" << bundlePaths;
0485         appendResources(&dirs, bundlePaths, true);
0486         Q_ASSERT(!dirs.isEmpty());
0487 #endif
0488 
0489         QStringList fallbackPaths;
0490         fallbackPaths << getApplicationRoot() + "/share/" + alias;
0491         fallbackPaths << getApplicationRoot() + "/share/krita/" + alias;
0492         appendResources(&dirs, fallbackPaths, true);
0493 
0494     }
0495 
0496     QStringList saveLocationList;
0497     saveLocationList << saveLocation(type, QString(), true);
0498     appendResources(&dirs, saveLocationList, true);
0499 
0500     dbgResources << "findDirs: type" << type << "resource" << dirs;
0501     return dirs;
0502 }
0503 
0504 
0505 QStringList KoResourcePaths::findAllResourcesInternal(const QString &type,
0506                                                       const QString &_filter,
0507                                                       SearchOptions options) const
0508 {
0509     dbgResources << "=====================================================";
0510     dbgResources << type << _filter << QStandardPaths::standardLocations(d->mapTypeToQStandardPaths(type));
0511 
0512     bool recursive = options & KoResourcePaths::Recursive;
0513 
0514     dbgResources << "findAllResources: type" << type << "filter" << _filter << "recursive" << recursive;
0515 
0516     QStringList aliases = d->aliases(type);
0517     QString filter = _filter;
0518 
0519     // In cases where the filter  is like "color-schemes/*.colors" instead of "*.kpp", used with unregistered resource types
0520     if (filter.indexOf('*') > 0) {
0521         aliases << filter.split('*').first();
0522         filter = '*' + filter.split('*')[1];
0523         dbgResources << "Split up alias" << aliases << "filter" << filter;
0524     }
0525 
0526     QStringList resources;
0527     if (aliases.isEmpty()) {
0528         QStringList standardResources =
0529                 QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type),
0530                                           filter, QStandardPaths::LocateFile);
0531         dbgResources << "standardResources" << standardResources;
0532         appendResources(&resources, standardResources, true);
0533         dbgResources << "1" << resources;
0534     }
0535 
0536     QStringList extraResourceDirs = findExtraResourceDirs();
0537 
0538     if (!extraResourceDirs.isEmpty()) {
0539         Q_FOREACH(const QString &extraResourceDir, extraResourceDirs) {
0540             if (aliases.isEmpty()) {
0541                 appendResources(&resources, filesInDir(extraResourceDir + '/' + type, filter, recursive), true);
0542             }
0543             else {
0544                 Q_FOREACH (const QString &alias, aliases) {
0545                     appendResources(&resources, filesInDir(extraResourceDir + '/' + alias + '/', filter, recursive), true);
0546                 }
0547             }
0548         }
0549 
0550     }
0551 
0552     dbgResources << "\tresources from qstandardpaths:" << resources.size();
0553 
0554     Q_FOREACH (const QString &alias, aliases) {
0555         dbgResources << "\t\talias:" << alias;
0556         QStringList dirs;
0557 
0558         QFileInfo dirInfo(alias);
0559         if (dirInfo.exists() && dirInfo.isDir() && dirInfo.isAbsolute()) {
0560             dirs << alias;
0561         } else {
0562             dirs << QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), alias, QStandardPaths::LocateDirectory)
0563                  << getInstallationPrefix() + "share/" + alias + "/"
0564                  << getInstallationPrefix() + "share/krita/" + alias + "/";
0565         }
0566 
0567         Q_FOREACH (const QString &dir, dirs) {
0568             appendResources(&resources,
0569                             filesInDir(dir, filter, recursive),
0570                             true);
0571         }
0572     }
0573 
0574     dbgResources << "\tresources also from aliases:" << resources.size();
0575 
0576     // if the original filter is "input/*", we only want share/input/* and share/krita/input/* here, but not
0577     // share/*. therefore, use _filter here instead of filter which was split into alias and "*".
0578     QFileInfo fi(_filter);
0579 
0580     QStringList prefixResources;
0581     prefixResources << filesInDir(getInstallationPrefix() + "share/" + fi.path(), fi.fileName(), false);
0582     prefixResources << filesInDir(getInstallationPrefix() + "share/krita/" + fi.path(), fi.fileName(), false);
0583     appendResources(&resources, prefixResources, true);
0584 
0585     dbgResources << "\tresources from installation:" << resources.size();
0586     dbgResources << "=====================================================";
0587 
0588     return resources;
0589 }
0590 
0591 QStringList KoResourcePaths::resourceDirsInternal(const QString &type)
0592 {
0593     QStringList resourceDirs;
0594     QStringList aliases = d->aliases(type);
0595 
0596     Q_FOREACH (const QString &alias, aliases) {
0597         QStringList aliasDirs;
0598 
0599         aliasDirs << QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), alias, QStandardPaths::LocateDirectory);
0600 
0601         aliasDirs << getInstallationPrefix() + "share/" + alias + "/"
0602                   << QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), alias, QStandardPaths::LocateDirectory);
0603         aliasDirs << getInstallationPrefix() + "share/krita/" + alias + "/"
0604                   << QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), alias, QStandardPaths::LocateDirectory);
0605 
0606         appendResources(&resourceDirs, aliasDirs, true);
0607     }
0608 
0609     dbgResources << "resourceDirs: type" << type << resourceDirs;
0610 
0611     return resourceDirs;
0612 }
0613 
0614 QString KoResourcePaths::saveLocationInternal(const QString &type, const QString &suffix, bool create)
0615 {
0616     QString path;
0617 
0618     bool useStandardLocation = false;
0619     const QStringList aliases = d->aliases(type);
0620     const QStandardPaths::StandardLocation location = d->mapTypeToQStandardPaths(type);
0621 
0622     if (location == QStandardPaths::AppDataLocation) {
0623         KConfigGroup cfg(KSharedConfig::openConfig(), "");
0624         path = cfg.readEntry(KisResourceLocator::resourceLocationKey, "");
0625     }
0626 
0627     if (path.isEmpty()) {
0628         path = QStandardPaths::writableLocation(location);
0629         useStandardLocation = true;
0630     }
0631 
0632 #ifndef Q_OS_ANDROID
0633     // on Android almost all config locations we save to are app specific,
0634     // and don't end with "krita".
0635     if (!path.endsWith("krita") && useStandardLocation) {
0636         path += "/krita";
0637     }
0638 #endif
0639 
0640     if (!aliases.isEmpty()) {
0641         path += '/' + aliases.first();
0642     } else {
0643 
0644         if (!suffix.isEmpty()) {
0645             path += "/" + suffix;
0646         }
0647     }
0648 
0649     QDir d(path);
0650     if (!d.exists() && create) {
0651         d.mkpath(path);
0652     }
0653     dbgResources << "saveLocation: type" << type << "suffix" << suffix << "create" << create << "path" << path;
0654 
0655     return path;
0656 }
0657 
0658 QString KoResourcePaths::locateInternal(const QString &type, const QString &filename)
0659 {
0660     QStringList aliases = d->aliases(type);
0661 
0662     QStringList locations;
0663     if (aliases.isEmpty()) {
0664         locations << QStandardPaths::locate(d->mapTypeToQStandardPaths(type), filename, QStandardPaths::LocateFile);
0665     }
0666 
0667     Q_FOREACH (const QString &alias, aliases) {
0668         locations << QStandardPaths::locate(d->mapTypeToQStandardPaths(type),
0669                                             (alias.endsWith('/') ? alias : alias + '/') + filename, QStandardPaths::LocateFile);
0670     }
0671     dbgResources << "locate: type" << type << "filename" << filename << "locations" << locations;
0672     if (locations.size() > 0) {
0673         return locations.first();
0674     }
0675     else {
0676         return "";
0677     }
0678 }
0679 
0680 QString KoResourcePaths::locateLocalInternal(const QString &type, const QString &filename, bool createDir)
0681 {
0682     QString path = saveLocationInternal(type, "", createDir);
0683     dbgResources << "locateLocal: type" << type << "filename" << filename << "CreateDir" << createDir << "path" << path;
0684     return path + '/' + filename;
0685 }
0686 
0687 QStringList KoResourcePaths::findExtraResourceDirs() const
0688 {
0689     QStringList extraResourceDirs =
0690         QString::fromUtf8(qgetenv("EXTRA_RESOURCE_DIRS"))
0691             .split(';', QString::SkipEmptyParts);
0692 
0693     const KConfigGroup cfg(KSharedConfig::openConfig(), "");
0694     const QString customPath =
0695         cfg.readEntry(KisResourceLocator::resourceLocationKey, "");
0696     if (!customPath.isEmpty()) {
0697         extraResourceDirs << customPath;
0698     }
0699 
0700     if (getAppDataLocation() != QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)) {
0701         extraResourceDirs << getAppDataLocation();
0702     }
0703 
0704     return extraResourceDirs;
0705 }