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