1 /*
2 * Copyright (c) 2015 Boudewijn Rempt <boud@valdyas.org>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library 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 GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18 #include "KoResourcePaths.h"
19
20 #include <QGlobalStatic>
21 #include <QString>
22 #include <QStringList>
23 #include <QMap>
24 #include <QStandardPaths>
25 #include <QDir>
26 #include <QFileInfo>
27 #include <QDebug>
28 #include <QApplication>
29 #include <QMutex>
30 #include "kis_debug.h"
31
32
Q_GLOBAL_STATIC(KoResourcePaths,s_instance)33 Q_GLOBAL_STATIC(KoResourcePaths, s_instance)
34
35 static QString cleanup(const QString &path)
36 {
37 return QDir::cleanPath(path);
38 }
39
40
cleanup(const QStringList & pathList)41 static QStringList cleanup(const QStringList &pathList)
42 {
43 QStringList cleanedPathList;
44 Q_FOREACH(const QString &path, pathList) {
45 cleanedPathList << cleanup(path);
46 }
47 return cleanedPathList;
48 }
49
50
cleanupDirs(const QString & path)51 static QString cleanupDirs(const QString &path)
52 {
53 return QDir::cleanPath(path) + '/';
54 }
55
cleanupDirs(const QStringList & pathList)56 static QStringList cleanupDirs(const QStringList &pathList)
57 {
58 QStringList cleanedPathList;
59 Q_FOREACH(const QString &path, pathList) {
60 cleanedPathList << cleanupDirs(path);
61 }
62 return cleanedPathList;
63 }
64
appendResources(QStringList * dst,const QStringList & src,bool eliminateDuplicates)65 void appendResources(QStringList *dst, const QStringList &src, bool eliminateDuplicates)
66 {
67 Q_FOREACH (const QString &resource, src) {
68 QString realPath = QDir::cleanPath(resource);
69 if (!eliminateDuplicates || !dst->contains(realPath)) {
70 *dst << realPath;
71 }
72 }
73 }
74
75
76 #ifdef Q_OS_WIN
77 static const Qt::CaseSensitivity cs = Qt::CaseInsensitive;
78 #else
79 static const Qt::CaseSensitivity cs = Qt::CaseSensitive;
80 #endif
81
82 #ifdef Q_OS_MACOS
83 #include <ApplicationServices/ApplicationServices.h>
84 #include <CoreFoundation/CoreFoundation.h>
85 #include <CoreServices/CoreServices.h>
86 #endif
87
getInstallationPrefix()88 QString getInstallationPrefix() {
89 #ifdef Q_OS_MACOS
90 QString appPath = qApp->applicationDirPath();
91
92 dbgResources << "1" << appPath;
93 appPath.chop(QString("MacOS/").length());
94 dbgResources << "2" << appPath;
95
96 bool makeInstall = QDir(appPath + "/../../../share/kritaplugins").exists();
97 bool inBundle = QDir(appPath + "/Resources/kritaplugins").exists();
98
99 dbgResources << "3. After make install" << makeInstall;
100 dbgResources << "4. In Bundle" << inBundle;
101
102 QString bundlePath;
103
104 if (inBundle) {
105 bundlePath = appPath + "/";
106 }
107 else if (makeInstall) {
108 appPath.chop(QString("Contents/").length());
109 bundlePath = appPath + "/../../";
110 }
111 else {
112 qFatal("Cannot calculate the bundle path from the app path");
113 }
114
115 dbgResources << ">>>>>>>>>>>" << bundlePath;
116 return bundlePath;
117 #else
118 #ifdef Q_OS_QWIN
119 QDir appdir(qApp->applicationDirPath());
120
121 // Corrects for mismatched case errors in path (qtdeclarative fails to load)
122 wchar_t buffer[1024];
123 QString absolute = appdir.absolutePath();
124 DWORD rv = ::GetShortPathName((wchar_t*)absolute.utf16(), buffer, 1024);
125 rv = ::GetLongPathName(buffer, buffer, 1024);
126 QString correctedPath((QChar *)buffer);
127 appdir.setPath(correctedPath);
128 appdir.cdUp();
129 return appdir.canonicalPath();
130 #else
131 #ifdef Q_OS_ANDROID
132 // qApp->applicationDirPath() isn't writable and android system won't allow
133 // any files other than libraries
134 return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/";
135 #else
136 return qApp->applicationDirPath() + "/../";
137 #endif
138 #endif
139 #endif
140 }
141
142 class Q_DECL_HIDDEN KoResourcePaths::Private {
143 public:
144 QMap<QString, QStringList> absolutes; // For each resource type, the list of absolute paths, from most local (most priority) to most global
145 QMap<QString, QStringList> relatives; // Same with relative paths
146
147 QMutex relativesMutex;
148 QMutex absolutesMutex;
149
aliases(const QString & type)150 QStringList aliases(const QString &type)
151 {
152 QStringList r;
153 QStringList a;
154 relativesMutex.lock();
155 if (relatives.contains(type)) {
156 r += relatives[type];
157 }
158 relativesMutex.unlock();
159 dbgResources << "\trelatives" << r;
160 absolutesMutex.lock();
161 if (absolutes.contains(type)) {
162 a += absolutes[type];
163 }
164 dbgResources << "\tabsolutes" << a;
165 absolutesMutex.unlock();
166
167 return r + a;
168 }
169
mapTypeToQStandardPaths(const QString & type)170 QStandardPaths::StandardLocation mapTypeToQStandardPaths(const QString &type)
171 {
172 if (type == "tmp") {
173 return QStandardPaths::TempLocation;
174 }
175 else if (type == "appdata") {
176 return QStandardPaths::AppDataLocation;
177 }
178 else if (type == "data") {
179 return QStandardPaths::AppDataLocation;
180 }
181 else if (type == "cache") {
182 return QStandardPaths::CacheLocation;
183 }
184 else if (type == "locale") {
185 return QStandardPaths::AppDataLocation;
186 }
187 else if (type == "genericdata") {
188 return QStandardPaths::GenericDataLocation;
189 }
190 else {
191 return QStandardPaths::AppDataLocation;
192 }
193 }
194 };
195
KoResourcePaths()196 KoResourcePaths::KoResourcePaths()
197 : d(new Private)
198 {
199 }
200
~KoResourcePaths()201 KoResourcePaths::~KoResourcePaths()
202 {
203 }
204
getApplicationRoot()205 QString KoResourcePaths::getApplicationRoot()
206 {
207 return getInstallationPrefix();
208 }
209
addResourceType(const char * type,const char * basetype,const QString & relativeName,bool priority)210 void KoResourcePaths::addResourceType(const char *type, const char *basetype,
211 const QString &relativeName, bool priority)
212 {
213 s_instance->addResourceTypeInternal(QString::fromLatin1(type), QString::fromLatin1(basetype), relativeName, priority);
214 }
215
addResourceDir(const char * type,const QString & dir,bool priority)216 void KoResourcePaths::addResourceDir(const char *type, const QString &dir, bool priority)
217 {
218 s_instance->addResourceDirInternal(QString::fromLatin1(type), dir, priority);
219 }
220
findResource(const char * type,const QString & fileName)221 QString KoResourcePaths::findResource(const char *type, const QString &fileName)
222 {
223 return cleanup(s_instance->findResourceInternal(QString::fromLatin1(type), fileName));
224 }
225
findDirs(const char * type)226 QStringList KoResourcePaths::findDirs(const char *type)
227 {
228 return cleanupDirs(s_instance->findDirsInternal(QString::fromLatin1(type)));
229 }
230
findAllResources(const char * type,const QString & filter,SearchOptions options)231 QStringList KoResourcePaths::findAllResources(const char *type,
232 const QString &filter,
233 SearchOptions options)
234 {
235 return cleanup(s_instance->findAllResourcesInternal(QString::fromLatin1(type), filter, options));
236 }
237
resourceDirs(const char * type)238 QStringList KoResourcePaths::resourceDirs(const char *type)
239 {
240 return cleanupDirs(s_instance->resourceDirsInternal(QString::fromLatin1(type)));
241 }
242
saveLocation(const char * type,const QString & suffix,bool create)243 QString KoResourcePaths::saveLocation(const char *type, const QString &suffix, bool create)
244 {
245 return cleanupDirs(s_instance->saveLocationInternal(QString::fromLatin1(type), suffix, create));
246 }
247
locate(const char * type,const QString & filename)248 QString KoResourcePaths::locate(const char *type, const QString &filename)
249 {
250 return cleanup(s_instance->locateInternal(QString::fromLatin1(type), filename));
251 }
252
locateLocal(const char * type,const QString & filename,bool createDir)253 QString KoResourcePaths::locateLocal(const char *type, const QString &filename, bool createDir)
254 {
255 return cleanup(s_instance->locateLocalInternal(QString::fromLatin1(type), filename, createDir));
256 }
257
addResourceTypeInternal(const QString & type,const QString & basetype,const QString & relativename,bool priority)258 void KoResourcePaths::addResourceTypeInternal(const QString &type, const QString &basetype,
259 const QString &relativename,
260 bool priority)
261 {
262 Q_UNUSED(basetype);
263 if (relativename.isEmpty()) return;
264
265 QString copy = relativename;
266
267 Q_ASSERT(basetype == "data");
268
269 if (!copy.endsWith(QLatin1Char('/'))) {
270 copy += QLatin1Char('/');
271 }
272
273 d->relativesMutex.lock();
274 QStringList &rels = d->relatives[type]; // find or insert
275
276 if (!rels.contains(copy, cs)) {
277 if (priority) {
278 rels.prepend(copy);
279 } else {
280 rels.append(copy);
281 }
282 }
283 d->relativesMutex.unlock();
284
285 dbgResources << "addResourceType: type" << type << "basetype" << basetype << "relativename" << relativename << "priority" << priority << d->relatives[type];
286 }
287
addResourceDirInternal(const QString & type,const QString & absdir,bool priority)288 void KoResourcePaths::addResourceDirInternal(const QString &type, const QString &absdir, bool priority)
289 {
290 if (absdir.isEmpty() || type.isEmpty()) return;
291
292 // find or insert entry in the map
293 QString copy = absdir;
294 if (copy.at(copy.length() - 1) != QLatin1Char('/')) {
295 copy += QLatin1Char('/');
296 }
297
298 d->absolutesMutex.lock();
299 QStringList &paths = d->absolutes[type];
300 if (!paths.contains(copy, cs)) {
301 if (priority) {
302 paths.prepend(copy);
303 } else {
304 paths.append(copy);
305 }
306 }
307 d->absolutesMutex.unlock();
308
309 dbgResources << "addResourceDir: type" << type << "absdir" << absdir << "priority" << priority << d->absolutes[type];
310 }
311
findResourceInternal(const QString & type,const QString & fileName)312 QString KoResourcePaths::findResourceInternal(const QString &type, const QString &fileName)
313 {
314 QStringList aliases = d->aliases(type);
315 dbgResources<< "aliases" << aliases << getApplicationRoot();
316 QString resource = QStandardPaths::locate(QStandardPaths::AppDataLocation, fileName, QStandardPaths::LocateFile);
317
318 if (resource.isEmpty()) {
319 Q_FOREACH (const QString &alias, aliases) {
320 resource = QStandardPaths::locate(d->mapTypeToQStandardPaths(type), alias + '/' + fileName, QStandardPaths::LocateFile);
321 dbgResources << "\t1" << resource;
322 if (QFile::exists(resource)) {
323 continue;
324 }
325 }
326 }
327 if (resource.isEmpty() || !QFile::exists(resource)) {
328 QString approot = getApplicationRoot();
329 Q_FOREACH (const QString &alias, aliases) {
330 resource = approot + "/share/" + alias + '/' + fileName;
331 dbgResources << "\t2" << resource;
332
333 if (QFile::exists(resource)) {
334 continue;
335 }
336 }
337 }
338 if (resource.isEmpty() || !QFile::exists(resource)) {
339 QString approot = getApplicationRoot();
340 Q_FOREACH (const QString &alias, aliases) {
341 resource = approot + "/share/krita/" + alias + '/' + fileName;
342 dbgResources << "\t3" << resource;
343 if (QFile::exists(resource)) {
344 continue;
345 }
346 }
347 }
348
349 if (resource.isEmpty() || !QFile::exists(resource)) {
350 QString extraResourceDirs = qgetenv("EXTRA_RESOURCE_DIRS");
351 if (!extraResourceDirs.isEmpty()) {
352 Q_FOREACH(const QString &extraResourceDir, extraResourceDirs.split(':', QString::SkipEmptyParts)) {
353 if (aliases.isEmpty()) {
354 resource = extraResourceDir + '/' + fileName;
355 dbgResources<< "\t4" << resource;
356 if (QFile::exists(resource)) {
357 continue;
358 }
359 }
360 else {
361 Q_FOREACH (const QString &alias, aliases) {
362 resource = extraResourceDir + '/' + alias + '/' + fileName;
363 dbgResources<< "\t4" << resource;
364 if (QFile::exists(resource)) {
365 continue;
366 }
367 }
368 }
369 }
370 }
371 }
372
373 dbgResources<< "findResource: type" << type << "filename" << fileName << "resource" << resource;
374 Q_ASSERT(!resource.isEmpty());
375 return resource;
376 }
377
378
filesInDir(const QString & startdir,const QString & filter,bool recursive)379 QStringList filesInDir(const QString &startdir, const QString & filter, bool recursive)
380 {
381 dbgResources << "filesInDir: startdir" << startdir << "filter" << filter << "recursive" << recursive;
382 QStringList result;
383
384 // First the entries in this path
385 QStringList nameFilters;
386 nameFilters << filter;
387 const QStringList fileNames = QDir(startdir).entryList(nameFilters, QDir::Files | QDir::CaseSensitive, QDir::Name);
388 dbgResources << "\tFound:" << fileNames.size() << ":" << fileNames;
389 Q_FOREACH (const QString &fileName, fileNames) {
390 QString file = startdir + '/' + fileName;
391 result << file;
392 }
393
394 // And then everything underneath, if recursive is specified
395 if (recursive) {
396 const QStringList entries = QDir(startdir).entryList(QDir::Dirs | QDir::NoDotAndDotDot);
397 Q_FOREACH (const QString &subdir, entries) {
398 dbgResources << "\tGoing to look in subdir" << subdir << "of" << startdir;
399 result << filesInDir(startdir + '/' + subdir, filter, recursive);
400 }
401 }
402 return result;
403 }
404
findDirsInternal(const QString & type)405 QStringList KoResourcePaths::findDirsInternal(const QString &type)
406 {
407 QStringList aliases = d->aliases(type);
408 dbgResources << type << aliases << d->mapTypeToQStandardPaths(type);
409
410 QStringList dirs;
411 QStringList standardDirs =
412 QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), "", QStandardPaths::LocateDirectory);
413 appendResources(&dirs, standardDirs, true);
414
415 Q_FOREACH (const QString &alias, aliases) {
416 QStringList aliasDirs =
417 QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), alias + '/', QStandardPaths::LocateDirectory);
418 appendResources(&dirs, aliasDirs, true);
419
420 #ifdef Q_OS_MACOS
421 dbgResources << "MAC:" << getApplicationRoot();
422 QStringList bundlePaths;
423 bundlePaths << getApplicationRoot() + "/share/krita/" + alias;
424 bundlePaths << getApplicationRoot() + "/../share/krita/" + alias;
425 dbgResources << "bundlePaths" << bundlePaths;
426 appendResources(&dirs, bundlePaths, true);
427 Q_ASSERT(!dirs.isEmpty());
428 #endif
429
430 QStringList fallbackPaths;
431 fallbackPaths << getApplicationRoot() + "/share/" + alias;
432 fallbackPaths << getApplicationRoot() + "/share/krita/" + alias;
433 appendResources(&dirs, fallbackPaths, true);
434
435 }
436 dbgResources << "findDirs: type" << type << "resource" << dirs;
437 return dirs;
438 }
439
440
findAllResourcesInternal(const QString & type,const QString & _filter,SearchOptions options) const441 QStringList KoResourcePaths::findAllResourcesInternal(const QString &type,
442 const QString &_filter,
443 SearchOptions options) const
444 {
445 dbgResources << "=====================================================";
446 dbgResources << type << _filter << QStandardPaths::standardLocations(d->mapTypeToQStandardPaths(type));
447
448 bool recursive = options & KoResourcePaths::Recursive;
449
450 dbgResources << "findAllResources: type" << type << "filter" << _filter << "recursive" << recursive;
451
452 QStringList aliases = d->aliases(type);
453 QString filter = _filter;
454
455 // In cases where the filter is like "color-schemes/*.colors" instead of "*.kpp", used with unregistered resource types
456 if (filter.indexOf('*') > 0) {
457 aliases << filter.split('*').first();
458 filter = '*' + filter.split('*')[1];
459 dbgResources << "Split up alias" << aliases << "filter" << filter;
460 }
461
462 QStringList resources;
463 if (aliases.isEmpty()) {
464 QStringList standardResources =
465 QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type),
466 filter, QStandardPaths::LocateFile);
467 dbgResources << "standardResources" << standardResources;
468 appendResources(&resources, standardResources, true);
469 dbgResources << "1" << resources;
470 }
471
472
473 QString extraResourceDirs = qgetenv("EXTRA_RESOURCE_DIRS");
474 dbgResources << "extraResourceDirs" << extraResourceDirs;
475 if (!extraResourceDirs.isEmpty()) {
476 Q_FOREACH(const QString &extraResourceDir, extraResourceDirs.split(':', QString::SkipEmptyParts)) {
477 if (aliases.isEmpty()) {
478 appendResources(&resources, filesInDir(extraResourceDir + '/' + type, filter, recursive), true);
479 }
480 else {
481 Q_FOREACH (const QString &alias, aliases) {
482 appendResources(&resources, filesInDir(extraResourceDir + '/' + alias + '/', filter, recursive), true);
483 }
484 }
485 }
486
487 }
488
489 dbgResources << "\tresources from qstandardpaths:" << resources.size();
490
491 Q_FOREACH (const QString &alias, aliases) {
492 dbgResources << "\t\talias:" << alias;
493 QStringList dirs;
494
495 QFileInfo dirInfo(alias);
496 if (dirInfo.exists() && dirInfo.isDir() && dirInfo.isAbsolute()) {
497 dirs << alias;
498 } else {
499 dirs << QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), alias, QStandardPaths::LocateDirectory)
500 << getInstallationPrefix() + "share/" + alias + "/"
501 << getInstallationPrefix() + "share/krita/" + alias + "/";
502 }
503
504 Q_FOREACH (const QString &dir, dirs) {
505 appendResources(&resources,
506 filesInDir(dir, filter, recursive),
507 true);
508 }
509 }
510
511 dbgResources << "\tresources also from aliases:" << resources.size();
512
513 // if the original filter is "input/*", we only want share/input/* and share/krita/input/* here, but not
514 // share/*. therefore, use _filter here instead of filter which was split into alias and "*".
515 QFileInfo fi(_filter);
516
517 QStringList prefixResources;
518 prefixResources << filesInDir(getInstallationPrefix() + "share/" + fi.path(), fi.fileName(), false);
519 prefixResources << filesInDir(getInstallationPrefix() + "share/krita/" + fi.path(), fi.fileName(), false);
520 appendResources(&resources, prefixResources, true);
521
522 dbgResources << "\tresources from installation:" << resources.size();
523 dbgResources << "=====================================================";
524
525 return resources;
526 }
527
resourceDirsInternal(const QString & type)528 QStringList KoResourcePaths::resourceDirsInternal(const QString &type)
529 {
530 QStringList resourceDirs;
531 QStringList aliases = d->aliases(type);
532
533 Q_FOREACH (const QString &alias, aliases) {
534 QStringList aliasDirs;
535
536 aliasDirs << QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), alias, QStandardPaths::LocateDirectory);
537
538 aliasDirs << getInstallationPrefix() + "share/" + alias + "/"
539 << QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), alias, QStandardPaths::LocateDirectory);
540 aliasDirs << getInstallationPrefix() + "share/krita/" + alias + "/"
541 << QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), alias, QStandardPaths::LocateDirectory);
542
543 appendResources(&resourceDirs, aliasDirs, true);
544 }
545
546 dbgResources << "resourceDirs: type" << type << resourceDirs;
547
548 return resourceDirs;
549 }
550
saveLocationInternal(const QString & type,const QString & suffix,bool create)551 QString KoResourcePaths::saveLocationInternal(const QString &type, const QString &suffix, bool create)
552 {
553 QStringList aliases = d->aliases(type);
554 QString path;
555 if (aliases.size() > 0) {
556 path = QStandardPaths::writableLocation(d->mapTypeToQStandardPaths(type)) + '/' + aliases.first();
557 }
558 else {
559 path = QStandardPaths::writableLocation(d->mapTypeToQStandardPaths(type));
560
561 #ifndef Q_OS_ANDROID
562 // on Android almost all config locations we save to are app specific,
563 // and don't end with "krita".
564 if (!path.endsWith("krita")) {
565 path += "/krita";
566 }
567 #endif
568
569 if (!suffix.isEmpty()) {
570 path += "/" + suffix;
571 }
572 }
573
574 QDir d(path);
575
576 if (!d.exists() && create) {
577 d.mkpath(path);
578 }
579 dbgResources << "saveLocation: type" << type << "suffix" << suffix << "create" << create << "path" << path;
580
581 return path;
582 }
583
locateInternal(const QString & type,const QString & filename)584 QString KoResourcePaths::locateInternal(const QString &type, const QString &filename)
585 {
586 QStringList aliases = d->aliases(type);
587
588 QStringList locations;
589 if (aliases.isEmpty()) {
590 locations << QStandardPaths::locate(d->mapTypeToQStandardPaths(type), filename, QStandardPaths::LocateFile);
591 }
592
593 Q_FOREACH (const QString &alias, aliases) {
594 locations << QStandardPaths::locate(d->mapTypeToQStandardPaths(type),
595 (alias.endsWith('/') ? alias : alias + '/') + filename, QStandardPaths::LocateFile);
596 }
597 dbgResources << "locate: type" << type << "filename" << filename << "locations" << locations;
598 if (locations.size() > 0) {
599 return locations.first();
600 }
601 else {
602 return "";
603 }
604 }
605
locateLocalInternal(const QString & type,const QString & filename,bool createDir)606 QString KoResourcePaths::locateLocalInternal(const QString &type, const QString &filename, bool createDir)
607 {
608 QString path = saveLocationInternal(type, "", createDir);
609 dbgResources << "locateLocal: type" << type << "filename" << filename << "CreateDir" << createDir << "path" << path;
610 return path + '/' + filename;
611 }
612