Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qtresourcemodel.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:significant reason:default
4
6#include "rcc_p.h"
7
8#include <QtCore/qstringlist.h>
9#include <QtCore/qhash.h>
10#include <QtCore/qmap.h>
11#include <QtCore/qresource.h>
12#include <QtCore/qfileinfo.h>
13#include <QtCore/qiodevice.h>
14#include <QtCore/qdir.h>
15#include <QtCore/qdebug.h>
16#include <QtCore/qbuffer.h>
17#include <QtCore/qfilesystemwatcher.h>
18
20
21enum { debugResourceModel = 0 };
22
23// ------------------- QtResourceSetPrivate
25{
26 QtResourceSet *q_ptr;
27 Q_DECLARE_PUBLIC(QtResourceSet)
28public:
30
31 QtResourceModel *m_resourceModel;
32};
33
34QtResourceSetPrivate::QtResourceSetPrivate(QtResourceModel *model) :
35 q_ptr(nullptr),
36 m_resourceModel(model)
37{
38}
39
40// -------------------- QtResourceModelPrivate
42{
43 QtResourceModel *q_ptr = nullptr;
44 Q_DECLARE_PUBLIC(QtResourceModel)
46public:
48 void activate(QtResourceSet *resourceSet, const QStringList &newPaths, int *errorCount = nullptr, QString *errorMessages = nullptr);
49 void removeOldPaths(QtResourceSet *resourceSet, const QStringList &newPaths);
50
53 QHash<QtResourceSet *, bool> m_resourceSetToReload; // while path is recreated it needs to be reregistered
54 // (it is - in the new current resource set, but when the path was used in
55 // other resource set
56 // then later when that resource set is activated it needs to be reregistered)
57 QHash<QtResourceSet *, bool> m_newlyCreated; // all created but not activated yet
58 // (if was active at some point and it's not now it will not be on that map)
61
63
64 QMap<QString, QStringList> m_pathToContents; // qrc path to its contents.
65 QMap<QString, QString> m_fileToQrc; // this map contains the content of active resource set only.
66 // Activating different resource set changes the contents.
67
71private:
72 void registerResourceSet(QtResourceSet *resourceSet);
73 void unregisterResourceSet(QtResourceSet *resourceSet);
74 void setWatcherEnabled(const QString &path, bool enable);
75 void addWatcher(const QString &path);
76 void removeWatcher(const QString &path);
77
78 void slotFileChanged(const QString &);
79
80 const QByteArray *createResource(const QString &path, QStringList *contents, int *errorCount, QIODevice &errorDevice) const;
81 void deleteResource(const QByteArray *data) const;
82};
83
84QtResourceModelPrivate::QtResourceModelPrivate() = default;
85
86// --------------------- QtResourceSet
87QtResourceSet::QtResourceSet() :
88 d_ptr(new QtResourceSetPrivate)
89{
90 d_ptr->q_ptr = this;
91}
92
93QtResourceSet::QtResourceSet(QtResourceModel *model) :
94 d_ptr(new QtResourceSetPrivate(model))
95{
96 d_ptr->q_ptr = this;
97}
98
99QtResourceSet::~QtResourceSet() = default;
100
101QStringList QtResourceSet::activeResourceFilePaths() const
102{
103 QtResourceSet *that = const_cast<QtResourceSet *>(this);
104 return d_ptr->m_resourceModel->d_ptr->m_resourceSetToPaths.value(that);
105}
106
107void QtResourceSet::activateResourceFilePaths(const QStringList &paths, int *errorCount, QString *errorMessages)
108{
109 d_ptr->m_resourceModel->d_ptr->activate(this, paths, errorCount, errorMessages);
110}
111
112bool QtResourceSet::isModified(const QString &path) const
113{
114 return d_ptr->m_resourceModel->isModified(path);
115}
116
117void QtResourceSet::setModified(const QString &path)
118{
119 d_ptr->m_resourceModel->setModified(path);
120}
121
122// ------------------- QtResourceModelPrivate
123const QByteArray *QtResourceModelPrivate::createResource(const QString &path, QStringList *contents, int *errorCount, QIODevice &errorDevice) const
124{
125 using ResourceDataFileMap = RCCResourceLibrary::ResourceDataFileMap;
126 const QByteArray *rc = nullptr;
127 *errorCount = -1;
128 contents->clear();
129 do {
130 // run RCC
131 RCCResourceLibrary library(3);
132 library.setVerbose(true);
133 library.setInputFiles(QStringList(path));
135
136 QBuffer buffer;
137 buffer.open(QIODevice::WriteOnly);
138 if (!library.readFiles(/* ignore errors*/ true, errorDevice))
139 break;
140 // return code cannot be fully trusted, might still be empty
141 const ResourceDataFileMap resMap = library.resourceDataFileMap();
142 if (!library.output(buffer, buffer /* tempfile, unused */, errorDevice))
143 break;
144
145 *errorCount = library.failedResources().size();
146 *contents = resMap.keys();
147
148 if (resMap.isEmpty())
149 break;
150
151 buffer.close();
152 rc = new QByteArray(buffer.data());
153 } while (false);
154
156 qDebug() << "createResource" << path << "returns data=" << rc << " hasWarnings=" << *errorCount;
157 return rc;
158}
159
160void QtResourceModelPrivate::deleteResource(const QByteArray *data) const
161{
162 if (data) {
164 qDebug() << "deleteResource";
165 delete data;
166 }
167}
168
169void QtResourceModelPrivate::registerResourceSet(QtResourceSet *resourceSet)
170{
171 if (!resourceSet)
172 return;
173
174 // unregister old paths (all because the order of registration is important), later it can be optimized a bit
175 const QStringList toRegister = resourceSet->activeResourceFilePaths();
176 for (const QString &path : toRegister) {
177 if (debugResourceModel)
178 qDebug() << "registerResourceSet " << path;
179 const auto itRcc = m_pathToData.constFind(path);
180 if (itRcc != m_pathToData.constEnd()) { // otherwise data was not created yet
181 const QByteArray *data = itRcc.value();
182 if (data) {
183 if (!QResource::registerResource(reinterpret_cast<const uchar *>(data->constData()))) {
184 qWarning() << "** WARNING: Failed to register " << path << " (QResource failure).";
185 } else {
186 const QStringList contents = m_pathToContents.value(path);
187 for (const QString &filePath : contents) {
188 if (!m_fileToQrc.contains(filePath)) // the first loaded resource has higher priority in qt resource system
189 m_fileToQrc.insert(filePath, path);
190 }
191 }
192 }
193 }
194 }
195}
196
197void QtResourceModelPrivate::unregisterResourceSet(QtResourceSet *resourceSet)
198{
199 if (!resourceSet)
200 return;
201
202 // unregister old paths (all because the order of registration is importans), later it can be optimized a bit
203 const QStringList toUnregister = resourceSet->activeResourceFilePaths();
204 for (const QString &path : toUnregister) {
205 if (debugResourceModel)
206 qDebug() << "unregisterResourceSet " << path;
207 const auto itRcc = m_pathToData.constFind(path);
208 if (itRcc != m_pathToData.constEnd()) { // otherwise data was not created yet
209 const QByteArray *data = itRcc.value();
210 if (data) {
211 if (!QResource::unregisterResource(reinterpret_cast<const uchar *>(itRcc.value()->constData())))
212 qWarning() << "** WARNING: Failed to unregister " << path << " (QResource failure).";
213 }
214 }
215 }
216 m_fileToQrc.clear();
217}
218
219void QtResourceModelPrivate::activate(QtResourceSet *resourceSet, const QStringList &newPaths, int *errorCountPtr, QString *errorMessages)
220{
221 if (debugResourceModel)
222 qDebug() << "activate " << resourceSet;
223 if (errorCountPtr)
224 *errorCountPtr = 0;
225 if (errorMessages)
226 errorMessages->clear();
227
228 QBuffer errorStream;
229 errorStream.open(QIODevice::WriteOnly);
230
231 int errorCount = 0;
232 int generatedCount = 0;
233 bool newResourceSetChanged = false;
234
235 if (resourceSet && resourceSet->activeResourceFilePaths() != newPaths && !m_newlyCreated.contains(resourceSet))
236 newResourceSetChanged = true;
237
238 auto newPathToData = m_pathToData;
239
240 for (const QString &path : newPaths) {
241 if (resourceSet && !m_pathToResourceSet[path].contains(resourceSet))
242 m_pathToResourceSet[path].append(resourceSet);
243 const auto itMod = m_pathToModified.find(path);
244 if (itMod == m_pathToModified.end() || itMod.value()) { // new path or path is already created, but needs to be recreated
245 QStringList contents;
246 int qrcErrorCount;
247 generatedCount++;
248 const QByteArray *data = createResource(path, &contents, &qrcErrorCount, errorStream);
249
250 newPathToData.insert(path, data);
251 if (qrcErrorCount) // Count single failed files as sort of 1/2 error
252 errorCount++;
253 addWatcher(path);
254
255 m_pathToModified.insert(path, false);
256 m_pathToContents.insert(path, contents);
257 newResourceSetChanged = true;
258 const auto itReload = m_pathToResourceSet.find(path);
259 if (itReload != m_pathToResourceSet.end()) {
260 const auto resources = itReload.value();
261 for (QtResourceSet *res : resources) {
262 if (res != resourceSet) {
263 m_resourceSetToReload[res] = true;
264 }
265 }
266 }
267 } else { // path is already created, don't need to recreate
268 }
269 }
270
271 const auto oldData = m_pathToData.values();
272 const auto newData = newPathToData.values();
273
274 QList<const QByteArray *> toDelete;
275 for (const QByteArray *array : oldData) {
276 if (array && !newData.contains(array))
277 toDelete.append(array);
278 }
279
280 // Nothing can fail below here?
281 if (generatedCount) {
282 if (errorCountPtr)
283 *errorCountPtr = errorCount;
284 errorStream.close();
285 const QString stderrOutput = QString::fromUtf8(errorStream.data());
286 if (debugResourceModel)
287 qDebug() << "Output: (" << errorCount << ")\n" << stderrOutput;
288 if (errorMessages)
289 *errorMessages = stderrOutput;
290 }
291 // register
292 const auto itReload = m_resourceSetToReload.find(resourceSet);
293 if (itReload != m_resourceSetToReload.end()) {
294 if (itReload.value()) {
295 newResourceSetChanged = true;
296 m_resourceSetToReload.insert(resourceSet, false);
297 }
298 }
299
300 QStringList oldActivePaths;
301 if (m_currentResourceSet)
302 oldActivePaths = m_currentResourceSet->activeResourceFilePaths();
303
304 const bool needReregister = (oldActivePaths != newPaths) || newResourceSetChanged;
305
306 const auto itNew = m_newlyCreated.find(resourceSet);
307 if (itNew != m_newlyCreated.end()) {
308 m_newlyCreated.remove(resourceSet);
309 if (needReregister)
310 newResourceSetChanged = true;
311 }
312
313 if (!newResourceSetChanged && !needReregister && (m_currentResourceSet == resourceSet)) {
314 for (const QByteArray *data : std::as_const(toDelete))
315 deleteResource(data);
316
317 return; // nothing changed
318 }
319
320 if (needReregister)
321 unregisterResourceSet(m_currentResourceSet);
322
323 for (const QByteArray *data : std::as_const(toDelete))
324 deleteResource(data);
325
326 m_pathToData = newPathToData;
327 m_currentResourceSet = resourceSet;
328
329 if (resourceSet)
330 removeOldPaths(resourceSet, newPaths);
331
332 if (needReregister)
333 registerResourceSet(m_currentResourceSet);
334
335 emit q_ptr->resourceSetActivated(m_currentResourceSet, newResourceSetChanged);
336
337 // deactivates the paths from old current resource set
338 // add new paths to the new current resource set
339 // reloads all paths which are marked as modified from the current resource set;
340 // activates the paths from current resource set
341 // emits resourceSetActivated() (don't emit only in case when old resource set is the same as new one
342 // AND no path was reloaded AND the list of paths is exactly the same)
343}
344
345void QtResourceModelPrivate::removeOldPaths(QtResourceSet *resourceSet, const QStringList &newPaths)
346{
347 const QStringList oldPaths = m_resourceSetToPaths.value(resourceSet);
348 if (oldPaths != newPaths) {
349 // remove old
350 for (const QString &oldPath : oldPaths) {
351 if (!newPaths.contains(oldPath)) {
352 const auto itRemove = m_pathToResourceSet.find(oldPath);
353 if (itRemove != m_pathToResourceSet.end()) {
354 const int idx = itRemove.value().indexOf(resourceSet);
355 if (idx >= 0)
356 itRemove.value().removeAt(idx);
357 if (itRemove.value().isEmpty()) {
358 const auto it = m_pathToData.find(oldPath);
359 if (it != m_pathToData.end())
360 deleteResource(it.value());
361 m_pathToResourceSet.erase(itRemove);
362 m_pathToModified.remove(oldPath);
363 m_pathToContents.remove(oldPath);
364 m_pathToData.remove(oldPath);
365 removeWatcher(oldPath);
366 }
367 }
368 }
369 }
370 m_resourceSetToPaths[resourceSet] = newPaths;
371 }
372}
373
374void QtResourceModelPrivate::setWatcherEnabled(const QString &path, bool enable)
375{
376 if (!enable) {
377 m_fileWatcher->removePath(path);
378 return;
379 }
380
381 QFileInfo fi(path);
382 if (fi.exists())
383 m_fileWatcher->addPath(path);
384}
385
386void QtResourceModelPrivate::addWatcher(const QString &path)
387{
388 const auto it = m_fileWatchedMap.constFind(path);
389 if (it != m_fileWatchedMap.constEnd() && !it.value())
390 return;
391
392 m_fileWatchedMap.insert(path, true);
394 return;
395 setWatcherEnabled(path, true);
396}
397
398void QtResourceModelPrivate::removeWatcher(const QString &path)
399{
400 if (!m_fileWatchedMap.contains(path))
401 return;
402
403 m_fileWatchedMap.remove(path);
405 return;
406 setWatcherEnabled(path, false);
407}
408
409void QtResourceModelPrivate::slotFileChanged(const QString &path)
410{
411 setWatcherEnabled(path, false);
412 emit q_ptr->qrcFileModifiedExternally(path);
413 setWatcherEnabled(path, true); //readd
414}
415
416// ----------------------- QtResourceModel
417QtResourceModel::QtResourceModel(QObject *parent) :
418 QObject(parent),
419 d_ptr(new QtResourceModelPrivate)
420{
421 d_ptr->q_ptr = this;
422
423 d_ptr->m_fileWatcher = new QFileSystemWatcher(this);
424 connect(d_ptr->m_fileWatcher, &QFileSystemWatcher::fileChanged,
425 this, [this](const QString &fileName) { d_ptr->slotFileChanged(fileName); });
426}
427
428QtResourceModel::~QtResourceModel()
429{
430 blockSignals(true);
431 const auto resourceList = resourceSets();
432 for (QtResourceSet *rs : resourceList)
433 removeResourceSet(rs);
434 blockSignals(false);
435}
436
437QStringList QtResourceModel::loadedQrcFiles() const
438{
439 return d_ptr->m_pathToModified.keys();
440}
441
442bool QtResourceModel::isModified(const QString &path) const
443{
444 return d_ptr->m_pathToModified.value(path, true);
445}
446
447void QtResourceModel::setModified(const QString &path)
448{
449 if (!d_ptr->m_pathToModified.contains(path))
450 return;
451
452 d_ptr->m_pathToModified[path] = true;
453 const auto it = d_ptr->m_pathToResourceSet.constFind(path);
454 if (it == d_ptr->m_pathToResourceSet.constEnd())
455 return;
456
457 const auto resourceList = it.value();
458 for (QtResourceSet *rs : resourceList)
459 d_ptr->m_resourceSetToReload.insert(rs, true);
460}
461
462QList<QtResourceSet *> QtResourceModel::resourceSets() const
463{
464 return d_ptr->m_resourceSetToPaths.keys();
465}
466
467QtResourceSet *QtResourceModel::currentResourceSet() const
468{
469 return d_ptr->m_currentResourceSet;
470}
471
472void QtResourceModel::setCurrentResourceSet(QtResourceSet *resourceSet, int *errorCount, QString *errorMessages)
473{
474 d_ptr->activate(resourceSet, d_ptr->m_resourceSetToPaths.value(resourceSet), errorCount, errorMessages);
475}
476
477QtResourceSet *QtResourceModel::addResourceSet(const QStringList &paths)
478{
479 QtResourceSet *newResource = new QtResourceSet(this);
480 d_ptr->m_resourceSetToPaths.insert(newResource, paths);
481 d_ptr->m_resourceSetToReload.insert(newResource, false);
482 d_ptr->m_newlyCreated.insert(newResource, true);
483 for (const QString &path : paths)
484 d_ptr->m_pathToResourceSet[path].append(newResource);
485 return newResource;
486}
487
488// TODO
489void QtResourceModel::removeResourceSet(QtResourceSet *resourceSet)
490{
491 if (!resourceSet)
492 return;
493 if (currentResourceSet() == resourceSet)
494 setCurrentResourceSet(nullptr);
495
496 // remove rcc files for those paths which are not used in any other resource set
497 d_ptr->removeOldPaths(resourceSet, QStringList());
498
499 d_ptr->m_resourceSetToPaths.remove(resourceSet);
500 d_ptr->m_resourceSetToReload.remove(resourceSet);
501 d_ptr->m_newlyCreated.remove(resourceSet);
502 delete resourceSet;
503}
504
505void QtResourceModel::reload(const QString &path, int *errorCount, QString *errorMessages)
506{
507 setModified(path);
508
509 d_ptr->activate(d_ptr->m_currentResourceSet, d_ptr->m_resourceSetToPaths.value(d_ptr->m_currentResourceSet), errorCount, errorMessages);
510}
511
512void QtResourceModel::reload(int *errorCount, QString *errorMessages)
513{
514 for (auto it = d_ptr->m_pathToModified.begin(), end = d_ptr->m_pathToModified.end(); it != end; ++it)
515 it.value() = true;
516
517 // empty resourceSets could be omitted here
518 for (auto itReload = d_ptr->m_resourceSetToReload.begin(), end = d_ptr->m_resourceSetToReload.end(); itReload != end; ++itReload)
519 itReload.value() = true;
520
521 d_ptr->activate(d_ptr->m_currentResourceSet, d_ptr->m_resourceSetToPaths.value(d_ptr->m_currentResourceSet), errorCount, errorMessages);
522}
523
524QMap<QString, QString> QtResourceModel::contents() const
525{
526 return d_ptr->m_fileToQrc;
527}
528
529QString QtResourceModel::qrcPath(const QString &file) const
530{
531 return d_ptr->m_fileToQrc.value(file);
532}
533
534void QtResourceModel::setWatcherEnabled(bool enable)
535{
536 if (d_ptr->m_fileWatcherEnabled == enable)
537 return;
538
539 d_ptr->m_fileWatcherEnabled = enable;
540
541 if (!d_ptr->m_fileWatchedMap.isEmpty())
542 d_ptr->setWatcherEnabled(d_ptr->m_fileWatchedMap.firstKey(), d_ptr->m_fileWatcherEnabled);
543}
544
545bool QtResourceModel::isWatcherEnabled() const
546{
547 return d_ptr->m_fileWatcherEnabled;
548}
549
550void QtResourceModel::setWatcherEnabled(const QString &path, bool enable)
551{
552 const auto it = d_ptr->m_fileWatchedMap.find(path);
553 if (it == d_ptr->m_fileWatchedMap.end())
554 return;
555
556 if (it.value() == enable)
557 return;
558
559 it.value() = enable;
560
561 if (!d_ptr->m_fileWatcherEnabled)
562 return;
563
564 d_ptr->setWatcherEnabled(it.key(), enable);
565}
566
567bool QtResourceModel::isWatcherEnabled(const QString &path)
568{
569 return d_ptr->m_fileWatchedMap.value(path, false);
570}
571
572QT_END_NAMESPACE
573
574#include "moc_qtresourcemodel_p.cpp"
QHash< QtResourceSet *, bool > m_resourceSetToReload
QMap< QString, QString > m_fileToQrc
QtResourceSet * m_currentResourceSet
QHash< QtResourceSet *, QStringList > m_resourceSetToPaths
void activate(QtResourceSet *resourceSet, const QStringList &newPaths, int *errorCount=nullptr, QString *errorMessages=nullptr)
QMap< QString, QStringList > m_pathToContents
QMap< QString, const QByteArray * > m_pathToData
QMap< QString, QList< QtResourceSet * > > m_pathToResourceSet
void removeOldPaths(QtResourceSet *resourceSet, const QStringList &newPaths)
QMap< QString, bool > m_fileWatchedMap
QFileSystemWatcher * m_fileWatcher
QHash< QtResourceSet *, bool > m_newlyCreated
QMap< QString, bool > m_pathToModified
QtResourceModel * m_resourceModel
bool readFiles(bool listMode, QIODevice &errorDevice)
Definition rcc.cpp:837
bool output(QIODevice &outDevice, QIODevice &tempDevice, QIODevice &errorDevice)
Definition rcc.cpp:973
void setVerbose(bool b)
Definition rcc.h:48
void setFormat(Format f)
Definition rcc.h:36
QHash< QString, QString > ResourceDataFileMap
Definition rcc_p.h:56
Combined button and popup list for selecting options.
@ debugResourceModel