1 /*
2  * Copyright (c) 2015 Boudewijn Rempt <boud@valdyas.org>
3  * Copyright (c) 2015 Friedrich W. H. Kossebau <kossebau@kde.org>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18  */
19 
20 // clazy:excludeall=qstring-arg
21 #include "KoResourcePaths.h"
22 
23 #include <QGlobalStatic>
24 #include <QStringList>
25 #include <QHash>
26 #include <QStandardPaths>
27 #include <QDir>
28 #include <QFileInfo>
29 #include <QDebug>
30 #include <QSet>
31 
32 
33 #ifdef Q_OS_WIN
34 static const Qt::CaseSensitivity cs = Qt::CaseInsensitive;
35 #else
36 static const Qt::CaseSensitivity cs = Qt::CaseSensitive;
37 #endif
38 
39 class KoResourcePathsImpl
40 {
41 public:
mapTypeToQStandardPaths(const QString & type)42     static QStandardPaths::StandardLocation mapTypeToQStandardPaths(const QString &type)
43     {
44         return
45             type == QLatin1String("data") ?    QStandardPaths::GenericDataLocation :
46             type == QLatin1String("config") ?  QStandardPaths::GenericConfigLocation :
47             type == QLatin1String("cache") ?   QStandardPaths::CacheLocation :
48             type == QLatin1String("tmp") ?     QStandardPaths::TempLocation :
49             type == QLatin1String("appdata") ? QStandardPaths::DataLocation :
50             type == QLatin1String("locale") ?  QStandardPaths::GenericDataLocation :
51             /* default */                      QStandardPaths::GenericDataLocation;
52     }
53 
54     KoResourcePathsImpl();
55     ~KoResourcePathsImpl();
56 
57     void addResourceTypeInternal(const QString &type, const QString &basetype,
58                                  const QString &relativeName, bool priority);
59 
60     void addResourceDirInternal(const QString &type, const QString &absdir, bool priority);
61 
62     QString findResourceInternal(const QString &type, const QString &fileName);
63 
64     QStringList findDirsInternal(const QString &type, const QString &relDir);
65 
66     QStringList findAllResourcesInternal(const QString &type,
67                                          const QString &filter = QString(),
68                                          KoResourcePaths::SearchOptions options = KoResourcePaths::NoSearchOptions) const;
69 
70     QStringList resourceDirsInternal(const QString &type);
71 
72     QString saveLocationInternal(const QString &type, const QString &suffix = QString(), bool create = true);
73 
74     QString locateLocalInternal(const QString &type, const QString &filename, bool createDir = false);
75 
76 private:
77     QHash<QString, QStringList> m_absolutes; // For each resource type, the list of absolute paths, from most local (most priority) to most global
78     QHash<QString, QStringList> m_relatives; // Same with relative paths
79 };
80 
KoResourcePathsImpl()81 KoResourcePathsImpl::KoResourcePathsImpl()
82 {
83 }
84 
~KoResourcePathsImpl()85 KoResourcePathsImpl::~KoResourcePathsImpl()
86 {
87 }
88 
89 
addResourceTypeInternal(const QString & type,const QString & basetype,const QString & relativename,bool priority)90 void KoResourcePathsImpl::addResourceTypeInternal(const QString &type, const QString &basetype,
91                                                   const QString &relativename,
92                                                   bool priority)
93 {
94     if (relativename.isEmpty()) return;
95 
96     QString copy = relativename;
97 
98     Q_ASSERT(basetype == "data");
99 
100     if (!copy.endsWith(QLatin1Char('/'))) {
101         copy += QLatin1Char('/');
102     }
103 
104     QStringList &rels = m_relatives[type]; // find or insert
105 
106     if (!rels.contains(copy, cs)) {
107         if (priority) {
108             rels.prepend(copy);
109         } else {
110             rels.append(copy);
111         }
112     }
113 
114     //qDebug() << "addResourceType: type" << type << "basetype" << basetype << "relativename" << relativename << "priority" << priority << m_relatives[type];
115 }
116 
addResourceDirInternal(const QString & type,const QString & absdir,bool priority)117 void KoResourcePathsImpl::addResourceDirInternal(const QString &type, const QString &absdir, bool priority)
118 {
119     if (absdir.isEmpty() || type.isEmpty()) return;
120 
121     // find or insert entry in the map
122     QString copy = absdir;
123     if (!copy.endsWith(QLatin1Char('/'))) {
124         copy += QLatin1Char('/');
125     }
126 
127     QStringList &paths = m_absolutes[type];
128     if (!paths.contains(copy, cs)) {
129         if (priority) {
130             paths.prepend(copy);
131         } else {
132             paths.append(copy);
133         }
134     }
135 
136     //qDebug() << "addResourceDir: type" << type << "absdir" << absdir << "priority" << priority << m_absolutes[type];
137 }
138 
findResourceInternal(const QString & type,const QString & fileName)139 QString KoResourcePathsImpl::findResourceInternal(const QString &type, const QString &fileName)
140 {
141     const QStandardPaths::StandardLocation location = mapTypeToQStandardPaths(type);
142     QString resource = QStandardPaths::locate(location, fileName, QStandardPaths::LocateFile);
143     if (resource.isEmpty()) {
144         foreach(const QString &relative, m_relatives.value(type)) {
145             resource = QStandardPaths::locate(location, relative + fileName, QStandardPaths::LocateFile);
146             if (!resource.isEmpty()) {
147                 break;
148             }
149         }
150     }
151     if (resource.isEmpty()) {
152         foreach(const QString &absolute, m_absolutes.value(type)) {
153             const QString filePath = absolute + fileName;
154             if (QFileInfo::exists(filePath)) {
155                 resource = filePath;
156                 break;
157             }
158         }
159     }
160     //Q_ASSERT(!resource.isEmpty());
161     //qDebug() << "findResource: type" << type << "filename" << fileName << "resource" << resource;
162     return resource;
163 }
164 
findDirsInternal(const QString & type,const QString & relDir)165 QStringList KoResourcePathsImpl::findDirsInternal(const QString &type, const QString &relDir)
166 {
167     const QStandardPaths::StandardLocation location = mapTypeToQStandardPaths(type);
168 
169     QStringList dirs = QStandardPaths::locateAll(location, relDir, QStandardPaths::LocateDirectory);
170 
171     foreach(const QString &relative, m_relatives.value(type)) {
172         dirs << QStandardPaths::locateAll(location, relative + relDir, QStandardPaths::LocateDirectory);
173     }
174 
175     foreach(const QString &absolute, m_absolutes.value(type)) {
176         const QString dirPath = absolute + relDir;
177         if (QDir(dirPath).exists()) {
178             dirs << dirPath;
179         }
180     }
181 
182     //Q_ASSERT(!dirs.isEmpty());
183     //qDebug() << "findDirs: type" << type << "relDir" << relDir<< "resource" << dirs;
184     return dirs;
185 }
186 
187 
filesInDir(const QString & startdir,const QString & filter,bool noduplicates,bool recursive)188 QStringList filesInDir(const QString &startdir, const QString & filter, bool noduplicates, bool recursive)
189 {
190     //qDebug() << "filesInDir: startdir" << startdir << "filter" << filter << "noduplicates" << noduplicates << "recursive" << recursive;
191     QStringList result;
192 
193     // First the entries in this path
194     QStringList nameFilters;
195     nameFilters << filter;
196     const QStringList fileNames = QDir(startdir).entryList(nameFilters, QDir::Files | QDir::CaseSensitive, QDir::Name);
197     //qDebug() << "\tFound:" << fileNames.size() << ":" << fileNames;
198     Q_FOREACH (const QString &fileName, fileNames) {
199         QString file = startdir + '/' + fileName;
200         result << file;
201     }
202 
203     // And then everything underneath, if recursive is specified
204     if (recursive) {
205         const QStringList entries = QDir(startdir).entryList(QDir::Dirs | QDir::NoDotAndDotDot);
206         Q_FOREACH (const QString &subdir, entries) {
207             //qDebug() << "\tGoing to look in subdir" << subdir << "of" << startdir;
208             result << filesInDir(startdir + '/' + subdir, filter, noduplicates, recursive);
209         }
210     }
211     return result;
212 }
213 
findAllResourcesInternal(const QString & type,const QString & _filter,KoResourcePaths::SearchOptions options) const214 QStringList KoResourcePathsImpl::findAllResourcesInternal(const QString &type,
215                                                           const QString &_filter,
216                                                           KoResourcePaths::SearchOptions options) const
217 {
218     //qDebug() << "=====================================================";
219 
220     bool noDuplicates = options & KoResourcePaths::NoDuplicates;
221     bool recursive = options & KoResourcePaths::Recursive;
222 
223     //qDebug() << "findAllResources: type" << type << "filter" << _filter << "no dups" << noDuplicates << "recursive" << recursive;
224 
225     const QStandardPaths::StandardLocation location = mapTypeToQStandardPaths(type);
226 
227     const QStringList relatives = m_relatives.value(type);
228     QString filter = _filter;
229     QString prefix;
230 
231     // In cases where the filter  is like "color-schemes/*.colors" instead of "*.kpp", used with unregistgered resource types
232     if (filter.indexOf('*') > 0) {
233         prefix = filter.split('*').first();
234         filter = '*' + filter.split('*')[1];
235         //qDebug() << "Split up alias" << relatives << "filter" << filter;
236     }
237 
238     QStringList resources;
239     if (relatives.isEmpty()) {
240         resources << QStandardPaths::locateAll(location, prefix + filter, QStandardPaths::LocateFile);
241     }
242 
243     ////qDebug() << "\tresources from qstandardpaths:" << resources.size();
244 
245 
246     foreach(const QString &relative, relatives) {
247         //qDebug() << "\t\relative:" << relative;
248         const QStringList dirs = QStandardPaths::locateAll(location, relative + prefix, QStandardPaths::LocateDirectory);
249         QSet<QString> s = QSet<QString>::fromList(dirs);
250 
251         //qDebug() << "\t\tdirs:" << dirs;
252         Q_FOREACH (const QString &dir, s) {
253             resources << filesInDir(dir, filter, noDuplicates, recursive);
254         }
255     }
256 
257     foreach(const QString &absolute, m_absolutes.value(type)) {
258         const QString dir = absolute + prefix;
259         if (QDir(dir).exists()) {
260             resources << filesInDir(dir, filter, noDuplicates, recursive);
261         }
262     }
263 
264     if (noDuplicates) {
265         QSet<QString> s = QSet<QString>::fromList(resources);
266         resources = s.toList();
267     }
268 
269     //qDebug() << "\tresources also from aliases:" << resources.size();
270     //qDebug() << "=====================================================";
271 
272     //Q_ASSERT(!resources.isEmpty());
273 
274     return resources;
275 }
276 
resourceDirsInternal(const QString & type)277 QStringList KoResourcePathsImpl::resourceDirsInternal(const QString &type)
278 {
279     //return KGlobal::dirs()->resourceDirs(type.toLatin1());
280     QStringList resourceDirs;
281 
282     const QStandardPaths::StandardLocation location = mapTypeToQStandardPaths(type);
283     foreach(const QString &relative, m_relatives.value(type)) {
284         resourceDirs << QStandardPaths::locateAll(location, relative, QStandardPaths::LocateDirectory);
285     }
286     foreach(const QString &absolute, m_absolutes.value(type)) {
287         if (QDir(absolute).exists()) {
288             resourceDirs << absolute;
289         }
290     }
291     //qDebug() << "resourceDirs: type" << type << resourceDirs;
292 
293     return resourceDirs;
294 }
295 
saveLocationInternal(const QString & type,const QString & suffix,bool create)296 QString KoResourcePathsImpl::saveLocationInternal(const QString &type, const QString &suffix, bool create)
297 {
298     QString path = QStandardPaths::writableLocation(mapTypeToQStandardPaths(type)) + '/' + suffix;
299     QDir d(path);
300     if (!d.exists() && create) {
301         d.mkpath(path);
302     }
303     //qDebug() << "saveLocation: type" << type << "suffix" << suffix << "create" << create << "path" << path;
304 
305     return path;
306 }
307 
locateLocalInternal(const QString & type,const QString & filename,bool createDir)308 QString KoResourcePathsImpl::locateLocalInternal(const QString &type, const QString &filename, bool createDir)
309 {
310     QString path = saveLocationInternal(type, "", createDir);
311     //qDebug() << "locateLocal: type" << type << "filename" << filename << "CreateDir" << createDir << "path" << path;
312     return path + '/' + filename;
313 }
314 
315 Q_GLOBAL_STATIC(KoResourcePathsImpl, s_instance);
316 
317 
addResourceType(const char * type,const char * basetype,const QString & relativeName,bool priority)318 void KoResourcePaths::addResourceType(const char *type, const char *basetype,
319                                       const QString &relativeName, bool priority)
320 {
321     s_instance->addResourceTypeInternal(QString::fromLatin1(type), QString::fromLatin1(basetype), relativeName, priority);
322 }
323 
addResourceDir(const char * type,const QString & dir,bool priority)324 void KoResourcePaths::addResourceDir(const char *type, const QString &dir, bool priority)
325 {
326     s_instance->addResourceDirInternal(QString::fromLatin1(type), dir, priority);
327 }
328 
findResource(const char * type,const QString & fileName)329 QString KoResourcePaths::findResource(const char *type, const QString &fileName)
330 {
331     return s_instance->findResourceInternal(QString::fromLatin1(type), fileName);
332 }
333 
findDirs(const char * type,const QString & reldir)334 QStringList KoResourcePaths::findDirs(const char *type, const QString &reldir)
335 {
336     return s_instance->findDirsInternal(QString::fromLatin1(type), reldir);
337 }
338 
findAllResources(const char * type,const QString & filter,SearchOptions options)339 QStringList KoResourcePaths::findAllResources(const char *type,
340                                               const QString &filter,
341                                               SearchOptions options)
342 {
343     return s_instance->findAllResourcesInternal(QString::fromLatin1(type), filter, options);
344 }
345 
resourceDirs(const char * type)346 QStringList KoResourcePaths::resourceDirs(const char *type)
347 {
348     return s_instance->resourceDirsInternal(QString::fromLatin1(type));
349 }
350 
saveLocation(const char * type,const QString & suffix,bool create)351 QString KoResourcePaths::saveLocation(const char *type, const QString &suffix, bool create)
352 {
353     return s_instance->saveLocationInternal(QString::fromLatin1(type), suffix, create);
354 }
355 
locate(const char * type,const QString & filename)356 QString KoResourcePaths::locate(const char *type, const QString &filename)
357 {
358     return s_instance->findResourceInternal(QString::fromLatin1(type), filename);
359 }
360 
locateLocal(const char * type,const QString & filename,bool createDir)361 QString KoResourcePaths::locateLocal(const char *type, const QString &filename, bool createDir)
362 {
363     return s_instance->locateLocalInternal(QString::fromLatin1(type), filename, createDir);
364 }
365