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