File indexing completed on 2024-05-19 04:27:43

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