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

source code of qttools/src/designer/src/lib/shared/qtresourcemodel.cpp