1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the Qt Designer of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qtresourcemodel_p.h"
45 #include <QtCore/QStringList>
46 #include <QtCore/QMap>
47 #include <QtCore/QResource>
48 #include <QtCore/QFileInfo>
49 #include <QtCore/QIODevice>
50 #include <QtCore/QDir>
51 #include <QtCore/QDebug>
52 #include <QtCore/QBuffer>
53 #include <QtCore/QFileSystemWatcher>
57 enum { debugResourceModel = 0 };
59 // ------------------- QtResourceSetPrivate
60 class QtResourceSetPrivate
63 Q_DECLARE_PUBLIC(QtResourceSet)
65 QtResourceSetPrivate(QtResourceModel *model = 0);
67 QtResourceModel *m_resourceModel;
70 QtResourceSetPrivate::QtResourceSetPrivate(QtResourceModel *model) :
72 m_resourceModel(model)
76 // -------------------- QtResourceModelPrivate
77 class QtResourceModelPrivate
79 QtResourceModel *q_ptr;
80 Q_DECLARE_PUBLIC(QtResourceModel)
81 Q_DISABLE_COPY(QtResourceModelPrivate)
83 QtResourceModelPrivate();
84 void activate(QtResourceSet *resourceSet, const QStringList &newPaths, int *errorCount = 0, QString *errorMessages = 0);
85 void removeOldPaths(QtResourceSet *resourceSet, const QStringList &newPaths);
87 QMap<QString, bool> m_pathToModified;
88 QMap<QtResourceSet *, QStringList> m_resourceSetToPaths;
89 QMap<QtResourceSet *, bool> m_resourceSetToReload; // while path is recreated it needs to be reregistered
90 // (it is - in the new current resource set, but when the path was used in
92 // then later when that resource set is activated it needs to be reregistered)
93 QMap<QtResourceSet *, bool> m_newlyCreated; // all created but not activated yet
94 // (if was active at some point and it's not now it will not be on that map)
95 QMap<QString, QList<QtResourceSet *> > m_pathToResourceSet;
96 QtResourceSet *m_currentResourceSet;
98 typedef QMap<QString, const QByteArray *> PathDataMap;
99 PathDataMap m_pathToData;
101 QMap<QString, QStringList> m_pathToContents; // qrc path to its contents.
102 QMap<QString, QString> m_fileToQrc; // this map contains the content of active resource set only.
103 // Activating different resource set changes the contents.
105 QFileSystemWatcher *m_fileWatcher;
106 bool m_fileWatcherEnabled;
107 QMap<QString, bool> m_fileWatchedMap;
109 void registerResourceSet(QtResourceSet *resourceSet);
110 void unregisterResourceSet(QtResourceSet *resourceSet);
111 void setWatcherEnabled(const QString &path, bool enable);
112 void addWatcher(const QString &path);
113 void removeWatcher(const QString &path);
115 void slotFileChanged(const QString &);
117 const QByteArray *createResource(const QString &path, QStringList *contents, int *errorCount, QIODevice &errorDevice) const;
118 void deleteResource(const QByteArray *data) const;
121 QtResourceModelPrivate::QtResourceModelPrivate() :
123 m_currentResourceSet(0),
125 m_fileWatcherEnabled(true)
129 // --------------------- QtResourceSet
130 QtResourceSet::QtResourceSet() :
131 d_ptr(new QtResourceSetPrivate)
136 QtResourceSet::QtResourceSet(QtResourceModel *model) :
137 d_ptr(new QtResourceSetPrivate(model))
142 QtResourceSet::~QtResourceSet()
146 QStringList QtResourceSet::activeResourceFilePaths() const
148 QtResourceSet *that = const_cast<QtResourceSet *>(this);
149 return d_ptr->m_resourceModel->d_ptr->m_resourceSetToPaths.value(that);
152 void QtResourceSet::activateResourceFilePaths(const QStringList &paths, int *errorCount, QString *errorMessages)
154 d_ptr->m_resourceModel->d_ptr->activate(this, paths, errorCount, errorMessages);
157 bool QtResourceSet::isModified(const QString &path) const
159 return d_ptr->m_resourceModel->isModified(path);
162 void QtResourceSet::setModified(const QString &path)
164 d_ptr->m_resourceModel->setModified(path);
167 // ------------------- QtResourceModelPrivate
168 const QByteArray *QtResourceModelPrivate::createResource(const QString &path, QStringList *contents, int *errorCount, QIODevice &errorDevice) const
170 typedef RCCResourceLibrary::ResourceDataFileMap ResourceDataFileMap;
171 const QByteArray *rc = 0;
176 RCCResourceLibrary library;
177 library.setVerbose(true);
178 library.setInputFiles(QStringList(path));
179 library.setFormat(RCCResourceLibrary::Binary);
182 buffer.open(QIODevice::WriteOnly);
183 if (!library.readFiles(/* ignore errors*/ true, errorDevice))
185 // return code cannot be fully trusted, might still be empty
186 const ResourceDataFileMap resMap = library.resourceDataFileMap();
187 if (!library.output(buffer, errorDevice))
190 *errorCount = library.failedResources().size();
191 *contents = resMap.keys();
197 rc = new QByteArray(buffer.data());
200 if (debugResourceModel)
201 qDebug() << "createResource" << path << "returns data=" << rc << " hasWarnings=" << *errorCount;
205 void QtResourceModelPrivate::deleteResource(const QByteArray *data) const
208 if (debugResourceModel)
209 qDebug() << "deleteResource";
214 void QtResourceModelPrivate::registerResourceSet(QtResourceSet *resourceSet)
219 // unregister old paths (all because the order of registration is important), later it can be optimized a bit
220 QStringList toRegister = resourceSet->activeResourceFilePaths();
221 QStringListIterator itRegister(toRegister);
222 while (itRegister.hasNext()) {
223 const QString path = itRegister.next();
224 if (debugResourceModel)
225 qDebug() << "registerResourceSet " << path;
226 const PathDataMap::const_iterator itRcc = m_pathToData.constFind(path);
227 if (itRcc != m_pathToData.constEnd()) { // otherwise data was not created yet
228 const QByteArray *data = itRcc.value();
230 if (!QResource::registerResource(reinterpret_cast<const uchar *>(data->constData()))) {
231 qWarning() << "** WARNING: Failed to register " << path << " (QResource failure).";
233 QStringList contents = m_pathToContents.value(path);
234 QStringListIterator itContents(contents);
235 while (itContents.hasNext()) {
236 const QString filePath = itContents.next();
237 if (!m_fileToQrc.contains(filePath)) // the first loaded resource has higher priority in qt resource system
238 m_fileToQrc.insert(filePath, path);
246 void QtResourceModelPrivate::unregisterResourceSet(QtResourceSet *resourceSet)
251 // unregister old paths (all because the order of registration is importans), later it can be optimized a bit
252 QStringList toUnregister = resourceSet->activeResourceFilePaths();
253 QStringListIterator itUnregister(toUnregister);
254 while (itUnregister.hasNext()) {
255 const QString path = itUnregister.next();
256 if (debugResourceModel)
257 qDebug() << "unregisterResourceSet " << path;
258 const PathDataMap::const_iterator itRcc = m_pathToData.constFind(path);
259 if (itRcc != m_pathToData.constEnd()) { // otherwise data was not created yet
260 const QByteArray *data = itRcc.value();
262 if (!QResource::unregisterResource(reinterpret_cast<const uchar *>(itRcc.value()->constData())))
263 qWarning() << "** WARNING: Failed to unregister " << path << " (QResource failure).";
270 void QtResourceModelPrivate::activate(QtResourceSet *resourceSet, const QStringList &newPaths, int *errorCountPtr, QString *errorMessages)
272 if (debugResourceModel)
273 qDebug() << "activate " << resourceSet;
277 errorMessages->clear();
280 errorStream.open(QIODevice::WriteOnly);
283 int generatedCount = 0;
284 bool newResourceSetChanged = false;
286 if (resourceSet && resourceSet->activeResourceFilePaths() != newPaths && !m_newlyCreated.contains(resourceSet))
287 newResourceSetChanged = true;
289 PathDataMap newPathToData = m_pathToData;
291 QStringListIterator itPath(newPaths);
292 while (itPath.hasNext()) {
293 const QString path = itPath.next();
294 if (resourceSet && !m_pathToResourceSet[path].contains(resourceSet))
295 m_pathToResourceSet[path].append(resourceSet);
296 const QMap<QString, bool>::iterator itMod = m_pathToModified.find(path);
297 if (itMod == m_pathToModified.end() || itMod.value()) { // new path or path is already created, but needs to be recreated
298 QStringList contents;
301 const QByteArray *data = createResource(path, &contents, &qrcErrorCount, errorStream);
303 newPathToData.insert(path, data);
304 if (qrcErrorCount) // Count single failed files as sort of 1/2 error
308 m_pathToModified.insert(path, false);
309 m_pathToContents.insert(path, contents);
310 newResourceSetChanged = true;
311 const QMap<QString, QList<QtResourceSet *> >::iterator itReload = m_pathToResourceSet.find(path);
312 if (itReload != m_pathToResourceSet.end()) {
313 QList<QtResourceSet *> resources = itReload.value();
314 QListIterator<QtResourceSet *> itRes(resources);
315 while (itRes.hasNext()) {
316 QtResourceSet *res = itRes.next();
317 if (res != resourceSet) {
318 m_resourceSetToReload[res] = true;
322 } else { // path is already created, don't need to recreate
326 QList<const QByteArray *> oldData = m_pathToData.values();
327 QList<const QByteArray *> newData = newPathToData.values();
329 QList<const QByteArray *> toDelete;
330 QListIterator<const QByteArray *> itOld(oldData);
331 if (itOld.hasNext()) {
332 const QByteArray *array = itOld.next();
333 if (array && !newData.contains(array))
334 toDelete.append(array);
337 // Nothing can fail below here?
338 if (generatedCount) {
340 *errorCountPtr = errorCount;
342 const QString stderrOutput = QString::fromUtf8(errorStream.data());
343 if (debugResourceModel)
344 qDebug() << "Output: (" << errorCount << ")\n" << stderrOutput;
346 *errorMessages = stderrOutput;
349 const QMap<QtResourceSet *, bool>::iterator itReload = m_resourceSetToReload.find(resourceSet);
350 if (itReload != m_resourceSetToReload.end()) {
351 if (itReload.value()) {
352 newResourceSetChanged = true;
353 m_resourceSetToReload.insert(resourceSet, false);
357 QStringList oldActivePaths;
358 if (m_currentResourceSet)
359 oldActivePaths = m_currentResourceSet->activeResourceFilePaths();
361 const bool needReregister = (oldActivePaths != newPaths) || newResourceSetChanged;
363 QMap<QtResourceSet *, bool>::iterator itNew = m_newlyCreated.find(resourceSet);
364 if (itNew != m_newlyCreated.end()) {
365 m_newlyCreated.remove(resourceSet);
367 newResourceSetChanged = true;
370 if (!newResourceSetChanged && !needReregister && (m_currentResourceSet == resourceSet)) {
371 foreach (const QByteArray *data, toDelete)
372 deleteResource(data);
374 return; // nothing changed
378 unregisterResourceSet(m_currentResourceSet);
380 foreach (const QByteArray *data, toDelete)
381 deleteResource(data);
383 m_pathToData = newPathToData;
384 m_currentResourceSet = resourceSet;
387 removeOldPaths(resourceSet, newPaths);
390 registerResourceSet(m_currentResourceSet);
392 emit q_ptr->resourceSetActivated(m_currentResourceSet, newResourceSetChanged);
394 // deactivates the paths from old current resource set
395 // add new paths to the new current resource set
396 // reloads all paths which are marked as modified from the current resource set;
397 // activates the paths from current resource set
398 // emits resourceSetActivated() (don't emit only in case when old resource set is the same as new one
399 // AND no path was reloaded AND the list of paths is exactly the same)
402 void QtResourceModelPrivate::removeOldPaths(QtResourceSet *resourceSet, const QStringList &newPaths)
404 QStringList oldPaths = m_resourceSetToPaths.value(resourceSet);
405 if (oldPaths != newPaths) {
407 QStringListIterator itOldPaths(oldPaths);
408 while (itOldPaths.hasNext()) {
409 QString oldPath = itOldPaths.next();
410 if (!newPaths.contains(oldPath)) {
411 const QMap<QString, QList<QtResourceSet *> >::iterator itRemove = m_pathToResourceSet.find(oldPath);
412 if (itRemove != m_pathToResourceSet.end()) {
413 const int idx = itRemove.value().indexOf(resourceSet);
415 itRemove.value().removeAt(idx);
416 if (itRemove.value().count() == 0) {
417 PathDataMap::iterator it = m_pathToData.find(oldPath);
418 if (it != m_pathToData.end())
419 deleteResource(it.value());
420 m_pathToResourceSet.erase(itRemove);
421 m_pathToModified.remove(oldPath);
422 m_pathToContents.remove(oldPath);
423 m_pathToData.remove(oldPath);
424 removeWatcher(oldPath);
429 m_resourceSetToPaths[resourceSet] = newPaths;
433 void QtResourceModelPrivate::setWatcherEnabled(const QString &path, bool enable)
436 m_fileWatcher->removePath(path);
442 m_fileWatcher->addPath(path);
445 void QtResourceModelPrivate::addWatcher(const QString &path)
447 QMap<QString, bool>::ConstIterator it = m_fileWatchedMap.constFind(path);
448 if (it != m_fileWatchedMap.constEnd() && it.value() == false)
451 m_fileWatchedMap.insert(path, true);
452 if (!m_fileWatcherEnabled)
454 setWatcherEnabled(path, true);
457 void QtResourceModelPrivate::removeWatcher(const QString &path)
459 if (!m_fileWatchedMap.contains(path))
462 m_fileWatchedMap.remove(path);
463 if (!m_fileWatcherEnabled)
465 setWatcherEnabled(path, false);
468 void QtResourceModelPrivate::slotFileChanged(const QString &path)
470 setWatcherEnabled(path, false);
471 emit q_ptr->qrcFileModifiedExternally(path);
472 setWatcherEnabled(path, true); //readd
475 // ----------------------- QtResourceModel
476 QtResourceModel::QtResourceModel(QObject *parent) :
478 d_ptr(new QtResourceModelPrivate)
482 d_ptr->m_fileWatcher = new QFileSystemWatcher(this);
483 connect(d_ptr->m_fileWatcher, SIGNAL(fileChanged(QString)),
484 this, SLOT(slotFileChanged(QString)));
487 QtResourceModel::~QtResourceModel()
490 QList<QtResourceSet *> resourceList = resourceSets();
491 QListIterator<QtResourceSet *> it(resourceList);
493 removeResourceSet(it.next());
497 QStringList QtResourceModel::loadedQrcFiles() const
499 return d_ptr->m_pathToModified.keys();
502 bool QtResourceModel::isModified(const QString &path) const
504 QMap<QString, bool>::const_iterator it = d_ptr->m_pathToModified.find(path);
505 if (it != d_ptr->m_pathToModified.constEnd())
510 void QtResourceModel::setModified(const QString &path)
512 QMap<QString, bool>::const_iterator itMod = d_ptr->m_pathToModified.find(path);
513 if (itMod == d_ptr->m_pathToModified.constEnd())
516 d_ptr->m_pathToModified[path] = true;
517 QMap<QString, QList<QtResourceSet *> >::const_iterator it = d_ptr->m_pathToResourceSet.constFind(path);
518 if (it == d_ptr->m_pathToResourceSet.constEnd())
521 QList<QtResourceSet *> resourceList = it.value();
522 QListIterator<QtResourceSet *> itReload(resourceList);
523 while (itReload.hasNext())
524 d_ptr->m_resourceSetToReload.insert(itReload.next(), true);
527 QList<QtResourceSet *> QtResourceModel::resourceSets() const
529 return d_ptr->m_resourceSetToPaths.keys();
532 QtResourceSet *QtResourceModel::currentResourceSet() const
534 return d_ptr->m_currentResourceSet;
537 void QtResourceModel::setCurrentResourceSet(QtResourceSet *resourceSet, int *errorCount, QString *errorMessages)
539 d_ptr->activate(resourceSet, d_ptr->m_resourceSetToPaths.value(resourceSet), errorCount, errorMessages);
542 QtResourceSet *QtResourceModel::addResourceSet(const QStringList &paths)
544 QtResourceSet *newResource = new QtResourceSet(this);
545 d_ptr->m_resourceSetToPaths.insert(newResource, paths);
546 d_ptr->m_resourceSetToReload.insert(newResource, false);
547 d_ptr->m_newlyCreated.insert(newResource, true);
548 QStringListIterator it(paths);
549 while (it.hasNext()) {
550 const QString path = it.next();
551 d_ptr->m_pathToResourceSet[path].append(newResource);
557 void QtResourceModel::removeResourceSet(QtResourceSet *resourceSet)
561 if (currentResourceSet() == resourceSet)
562 setCurrentResourceSet(0);
564 // remove rcc files for those paths which are not used in any other resource set
565 d_ptr->removeOldPaths(resourceSet, QStringList());
567 d_ptr->m_resourceSetToPaths.remove(resourceSet);
568 d_ptr->m_resourceSetToReload.remove(resourceSet);
569 d_ptr->m_newlyCreated.remove(resourceSet);
573 void QtResourceModel::reload(const QString &path, int *errorCount, QString *errorMessages)
577 d_ptr->activate(d_ptr->m_currentResourceSet, d_ptr->m_resourceSetToPaths.value(d_ptr->m_currentResourceSet), errorCount, errorMessages);
580 void QtResourceModel::reload(int *errorCount, QString *errorMessages)
582 QMap<QString, bool>::iterator it = d_ptr->m_pathToModified.begin();
583 QMap<QString, bool>::iterator itEnd = d_ptr->m_pathToModified.end(); // will it be valid when I iterate the map and change it???
584 while (it != itEnd) {
585 it = d_ptr->m_pathToModified.insert(it.key(), true);
589 QMap<QtResourceSet *, bool>::iterator itReload = d_ptr->m_resourceSetToReload.begin();
590 QMap<QtResourceSet *, bool>::iterator itReloadEnd = d_ptr->m_resourceSetToReload.end();
591 while (itReload != itReloadEnd) {
592 itReload = d_ptr->m_resourceSetToReload.insert(itReload.key(), true); // empty resourceSets could be omitted here
596 d_ptr->activate(d_ptr->m_currentResourceSet, d_ptr->m_resourceSetToPaths.value(d_ptr->m_currentResourceSet), errorCount, errorMessages);
599 QMap<QString, QString> QtResourceModel::contents() const
601 return d_ptr->m_fileToQrc;
604 QString QtResourceModel::qrcPath(const QString &file) const
606 return d_ptr->m_fileToQrc.value(file);
609 void QtResourceModel::setWatcherEnabled(bool enable)
611 if (d_ptr->m_fileWatcherEnabled == enable)
614 d_ptr->m_fileWatcherEnabled = enable;
616 QMapIterator<QString, bool> it(d_ptr->m_fileWatchedMap);
618 d_ptr->setWatcherEnabled(it.next().key(), d_ptr->m_fileWatcherEnabled);
621 bool QtResourceModel::isWatcherEnabled() const
623 return d_ptr->m_fileWatcherEnabled;
626 void QtResourceModel::setWatcherEnabled(const QString &path, bool enable)
628 QMap<QString, bool>::Iterator it = d_ptr->m_fileWatchedMap.find(path);
629 if (it == d_ptr->m_fileWatchedMap.end())
632 if (it.value() == enable)
637 if (!d_ptr->m_fileWatcherEnabled)
640 d_ptr->setWatcherEnabled(it.key(), enable);
643 bool QtResourceModel::isWatcherEnabled(const QString &path)
645 return d_ptr->m_fileWatchedMap.value(path, false);
650 #include "moc_qtresourcemodel_p.cpp"