/**************************************************************************** ** ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/ ** ** This file is part of the Qt Designer of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser General Public ** License version 2.1 as published by the Free Software Foundation and ** appearing in the file LICENSE.LGPL included in the packaging of this ** file. Please review the following information to ensure the GNU Lesser ** General Public License version 2.1 requirements will be met: ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU General ** Public License version 3.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of this ** file. Please review the following information to ensure the GNU General ** Public License version 3.0 requirements will be met: ** http://www.gnu.org/copyleft/gpl.html. ** ** Other Usage ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qtresourcemodel_p.h" #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE enum { debugResourceModel = 0 }; // ------------------- QtResourceSetPrivate class QtResourceSetPrivate { QtResourceSet *q_ptr; Q_DECLARE_PUBLIC(QtResourceSet) public: QtResourceSetPrivate(QtResourceModel *model = 0); QtResourceModel *m_resourceModel; }; QtResourceSetPrivate::QtResourceSetPrivate(QtResourceModel *model) : q_ptr(0), m_resourceModel(model) { } // -------------------- QtResourceModelPrivate class QtResourceModelPrivate { QtResourceModel *q_ptr; Q_DECLARE_PUBLIC(QtResourceModel) Q_DISABLE_COPY(QtResourceModelPrivate) public: QtResourceModelPrivate(); void activate(QtResourceSet *resourceSet, const QStringList &newPaths, int *errorCount = 0, QString *errorMessages = 0); void removeOldPaths(QtResourceSet *resourceSet, const QStringList &newPaths); QMap m_pathToModified; QMap m_resourceSetToPaths; QMap m_resourceSetToReload; // while path is recreated it needs to be reregistered // (it is - in the new current resource set, but when the path was used in // other resource set // then later when that resource set is activated it needs to be reregistered) QMap m_newlyCreated; // all created but not activated yet // (if was active at some point and it's not now it will not be on that map) QMap > m_pathToResourceSet; QtResourceSet *m_currentResourceSet; typedef QMap PathDataMap; PathDataMap m_pathToData; QMap m_pathToContents; // qrc path to its contents. QMap m_fileToQrc; // this map contains the content of active resource set only. // Activating different resource set changes the contents. QFileSystemWatcher *m_fileWatcher; bool m_fileWatcherEnabled; QMap m_fileWatchedMap; private: void registerResourceSet(QtResourceSet *resourceSet); void unregisterResourceSet(QtResourceSet *resourceSet); void setWatcherEnabled(const QString &path, bool enable); void addWatcher(const QString &path); void removeWatcher(const QString &path); void slotFileChanged(const QString &); const QByteArray *createResource(const QString &path, QStringList *contents, int *errorCount, QIODevice &errorDevice) const; void deleteResource(const QByteArray *data) const; }; QtResourceModelPrivate::QtResourceModelPrivate() : q_ptr(0), m_currentResourceSet(0), m_fileWatcher(0), m_fileWatcherEnabled(true) { } // --------------------- QtResourceSet QtResourceSet::QtResourceSet() : d_ptr(new QtResourceSetPrivate) { d_ptr->q_ptr = this; } QtResourceSet::QtResourceSet(QtResourceModel *model) : d_ptr(new QtResourceSetPrivate(model)) { d_ptr->q_ptr = this; } QtResourceSet::~QtResourceSet() { } QStringList QtResourceSet::activeResourceFilePaths() const { QtResourceSet *that = const_cast(this); return d_ptr->m_resourceModel->d_ptr->m_resourceSetToPaths.value(that); } void QtResourceSet::activateResourceFilePaths(const QStringList &paths, int *errorCount, QString *errorMessages) { d_ptr->m_resourceModel->d_ptr->activate(this, paths, errorCount, errorMessages); } bool QtResourceSet::isModified(const QString &path) const { return d_ptr->m_resourceModel->isModified(path); } void QtResourceSet::setModified(const QString &path) { d_ptr->m_resourceModel->setModified(path); } // ------------------- QtResourceModelPrivate const QByteArray *QtResourceModelPrivate::createResource(const QString &path, QStringList *contents, int *errorCount, QIODevice &errorDevice) const { typedef RCCResourceLibrary::ResourceDataFileMap ResourceDataFileMap; const QByteArray *rc = 0; *errorCount = -1; contents->clear(); do { // run RCC RCCResourceLibrary library; library.setVerbose(true); library.setInputFiles(QStringList(path)); library.setFormat(RCCResourceLibrary::Binary); QBuffer buffer; buffer.open(QIODevice::WriteOnly); if (!library.readFiles(/* ignore errors*/ true, errorDevice)) break; // return code cannot be fully trusted, might still be empty const ResourceDataFileMap resMap = library.resourceDataFileMap(); if (!library.output(buffer, errorDevice)) break; *errorCount = library.failedResources().size(); *contents = resMap.keys(); if (resMap.empty()) break; buffer.close(); rc = new QByteArray(buffer.data()); } while (false); if (debugResourceModel) qDebug() << "createResource" << path << "returns data=" << rc << " hasWarnings=" << *errorCount; return rc; } void QtResourceModelPrivate::deleteResource(const QByteArray *data) const { if (data) { if (debugResourceModel) qDebug() << "deleteResource"; delete data; } } void QtResourceModelPrivate::registerResourceSet(QtResourceSet *resourceSet) { if (!resourceSet) return; // unregister old paths (all because the order of registration is important), later it can be optimized a bit QStringList toRegister = resourceSet->activeResourceFilePaths(); QStringListIterator itRegister(toRegister); while (itRegister.hasNext()) { const QString path = itRegister.next(); if (debugResourceModel) qDebug() << "registerResourceSet " << path; const PathDataMap::const_iterator itRcc = m_pathToData.constFind(path); if (itRcc != m_pathToData.constEnd()) { // otherwise data was not created yet const QByteArray *data = itRcc.value(); if (data) { if (!QResource::registerResource(reinterpret_cast(data->constData()))) { qWarning() << "** WARNING: Failed to register " << path << " (QResource failure)."; } else { QStringList contents = m_pathToContents.value(path); QStringListIterator itContents(contents); while (itContents.hasNext()) { const QString filePath = itContents.next(); if (!m_fileToQrc.contains(filePath)) // the first loaded resource has higher priority in qt resource system m_fileToQrc.insert(filePath, path); } } } } } } void QtResourceModelPrivate::unregisterResourceSet(QtResourceSet *resourceSet) { if (!resourceSet) return; // unregister old paths (all because the order of registration is importans), later it can be optimized a bit QStringList toUnregister = resourceSet->activeResourceFilePaths(); QStringListIterator itUnregister(toUnregister); while (itUnregister.hasNext()) { const QString path = itUnregister.next(); if (debugResourceModel) qDebug() << "unregisterResourceSet " << path; const PathDataMap::const_iterator itRcc = m_pathToData.constFind(path); if (itRcc != m_pathToData.constEnd()) { // otherwise data was not created yet const QByteArray *data = itRcc.value(); if (data) { if (!QResource::unregisterResource(reinterpret_cast(itRcc.value()->constData()))) qWarning() << "** WARNING: Failed to unregister " << path << " (QResource failure)."; } } } m_fileToQrc.clear(); } void QtResourceModelPrivate::activate(QtResourceSet *resourceSet, const QStringList &newPaths, int *errorCountPtr, QString *errorMessages) { if (debugResourceModel) qDebug() << "activate " << resourceSet; if (errorCountPtr) *errorCountPtr = 0; if (errorMessages) errorMessages->clear(); QBuffer errorStream; errorStream.open(QIODevice::WriteOnly); int errorCount = 0; int generatedCount = 0; bool newResourceSetChanged = false; if (resourceSet && resourceSet->activeResourceFilePaths() != newPaths && !m_newlyCreated.contains(resourceSet)) newResourceSetChanged = true; PathDataMap newPathToData = m_pathToData; QStringListIterator itPath(newPaths); while (itPath.hasNext()) { const QString path = itPath.next(); if (resourceSet && !m_pathToResourceSet[path].contains(resourceSet)) m_pathToResourceSet[path].append(resourceSet); const QMap::iterator itMod = m_pathToModified.find(path); if (itMod == m_pathToModified.end() || itMod.value()) { // new path or path is already created, but needs to be recreated QStringList contents; int qrcErrorCount; generatedCount++; const QByteArray *data = createResource(path, &contents, &qrcErrorCount, errorStream); newPathToData.insert(path, data); if (qrcErrorCount) // Count single failed files as sort of 1/2 error errorCount++; addWatcher(path); m_pathToModified.insert(path, false); m_pathToContents.insert(path, contents); newResourceSetChanged = true; const QMap >::iterator itReload = m_pathToResourceSet.find(path); if (itReload != m_pathToResourceSet.end()) { QList resources = itReload.value(); QListIterator itRes(resources); while (itRes.hasNext()) { QtResourceSet *res = itRes.next(); if (res != resourceSet) { m_resourceSetToReload[res] = true; } } } } else { // path is already created, don't need to recreate } } QList oldData = m_pathToData.values(); QList newData = newPathToData.values(); QList toDelete; QListIterator itOld(oldData); if (itOld.hasNext()) { const QByteArray *array = itOld.next(); if (array && !newData.contains(array)) toDelete.append(array); } // Nothing can fail below here? if (generatedCount) { if (errorCountPtr) *errorCountPtr = errorCount; errorStream.close(); const QString stderrOutput = QString::fromUtf8(errorStream.data()); if (debugResourceModel) qDebug() << "Output: (" << errorCount << ")\n" << stderrOutput; if (errorMessages) *errorMessages = stderrOutput; } // register const QMap::iterator itReload = m_resourceSetToReload.find(resourceSet); if (itReload != m_resourceSetToReload.end()) { if (itReload.value()) { newResourceSetChanged = true; m_resourceSetToReload.insert(resourceSet, false); } } QStringList oldActivePaths; if (m_currentResourceSet) oldActivePaths = m_currentResourceSet->activeResourceFilePaths(); const bool needReregister = (oldActivePaths != newPaths) || newResourceSetChanged; QMap::iterator itNew = m_newlyCreated.find(resourceSet); if (itNew != m_newlyCreated.end()) { m_newlyCreated.remove(resourceSet); if (needReregister) newResourceSetChanged = true; } if (!newResourceSetChanged && !needReregister && (m_currentResourceSet == resourceSet)) { foreach (const QByteArray *data, toDelete) deleteResource(data); return; // nothing changed } if (needReregister) unregisterResourceSet(m_currentResourceSet); foreach (const QByteArray *data, toDelete) deleteResource(data); m_pathToData = newPathToData; m_currentResourceSet = resourceSet; if (resourceSet) removeOldPaths(resourceSet, newPaths); if (needReregister) registerResourceSet(m_currentResourceSet); emit q_ptr->resourceSetActivated(m_currentResourceSet, newResourceSetChanged); // deactivates the paths from old current resource set // add new paths to the new current resource set // reloads all paths which are marked as modified from the current resource set; // activates the paths from current resource set // emits resourceSetActivated() (don't emit only in case when old resource set is the same as new one // AND no path was reloaded AND the list of paths is exactly the same) } void QtResourceModelPrivate::removeOldPaths(QtResourceSet *resourceSet, const QStringList &newPaths) { QStringList oldPaths = m_resourceSetToPaths.value(resourceSet); if (oldPaths != newPaths) { // remove old QStringListIterator itOldPaths(oldPaths); while (itOldPaths.hasNext()) { QString oldPath = itOldPaths.next(); if (!newPaths.contains(oldPath)) { const QMap >::iterator itRemove = m_pathToResourceSet.find(oldPath); if (itRemove != m_pathToResourceSet.end()) { const int idx = itRemove.value().indexOf(resourceSet); if (idx >= 0) itRemove.value().removeAt(idx); if (itRemove.value().count() == 0) { PathDataMap::iterator it = m_pathToData.find(oldPath); if (it != m_pathToData.end()) deleteResource(it.value()); m_pathToResourceSet.erase(itRemove); m_pathToModified.remove(oldPath); m_pathToContents.remove(oldPath); m_pathToData.remove(oldPath); removeWatcher(oldPath); } } } } m_resourceSetToPaths[resourceSet] = newPaths; } } void QtResourceModelPrivate::setWatcherEnabled(const QString &path, bool enable) { if (!enable) { m_fileWatcher->removePath(path); return; } QFileInfo fi(path); if (fi.exists()) m_fileWatcher->addPath(path); } void QtResourceModelPrivate::addWatcher(const QString &path) { QMap::ConstIterator it = m_fileWatchedMap.constFind(path); if (it != m_fileWatchedMap.constEnd() && it.value() == false) return; m_fileWatchedMap.insert(path, true); if (!m_fileWatcherEnabled) return; setWatcherEnabled(path, true); } void QtResourceModelPrivate::removeWatcher(const QString &path) { if (!m_fileWatchedMap.contains(path)) return; m_fileWatchedMap.remove(path); if (!m_fileWatcherEnabled) return; setWatcherEnabled(path, false); } void QtResourceModelPrivate::slotFileChanged(const QString &path) { setWatcherEnabled(path, false); emit q_ptr->qrcFileModifiedExternally(path); setWatcherEnabled(path, true); //readd } // ----------------------- QtResourceModel QtResourceModel::QtResourceModel(QObject *parent) : QObject(parent), d_ptr(new QtResourceModelPrivate) { d_ptr->q_ptr = this; d_ptr->m_fileWatcher = new QFileSystemWatcher(this); connect(d_ptr->m_fileWatcher, SIGNAL(fileChanged(QString)), this, SLOT(slotFileChanged(QString))); } QtResourceModel::~QtResourceModel() { blockSignals(true); QList resourceList = resourceSets(); QListIterator it(resourceList); while (it.hasNext()) removeResourceSet(it.next()); blockSignals(false); } QStringList QtResourceModel::loadedQrcFiles() const { return d_ptr->m_pathToModified.keys(); } bool QtResourceModel::isModified(const QString &path) const { QMap::const_iterator it = d_ptr->m_pathToModified.find(path); if (it != d_ptr->m_pathToModified.constEnd()) return it.value(); return true; } void QtResourceModel::setModified(const QString &path) { QMap::const_iterator itMod = d_ptr->m_pathToModified.find(path); if (itMod == d_ptr->m_pathToModified.constEnd()) return; d_ptr->m_pathToModified[path] = true; QMap >::const_iterator it = d_ptr->m_pathToResourceSet.constFind(path); if (it == d_ptr->m_pathToResourceSet.constEnd()) return; QList resourceList = it.value(); QListIterator itReload(resourceList); while (itReload.hasNext()) d_ptr->m_resourceSetToReload.insert(itReload.next(), true); } QList QtResourceModel::resourceSets() const { return d_ptr->m_resourceSetToPaths.keys(); } QtResourceSet *QtResourceModel::currentResourceSet() const { return d_ptr->m_currentResourceSet; } void QtResourceModel::setCurrentResourceSet(QtResourceSet *resourceSet, int *errorCount, QString *errorMessages) { d_ptr->activate(resourceSet, d_ptr->m_resourceSetToPaths.value(resourceSet), errorCount, errorMessages); } QtResourceSet *QtResourceModel::addResourceSet(const QStringList &paths) { QtResourceSet *newResource = new QtResourceSet(this); d_ptr->m_resourceSetToPaths.insert(newResource, paths); d_ptr->m_resourceSetToReload.insert(newResource, false); d_ptr->m_newlyCreated.insert(newResource, true); QStringListIterator it(paths); while (it.hasNext()) { const QString path = it.next(); d_ptr->m_pathToResourceSet[path].append(newResource); } return newResource; } // TODO void QtResourceModel::removeResourceSet(QtResourceSet *resourceSet) { if (!resourceSet) return; if (currentResourceSet() == resourceSet) setCurrentResourceSet(0); // remove rcc files for those paths which are not used in any other resource set d_ptr->removeOldPaths(resourceSet, QStringList()); d_ptr->m_resourceSetToPaths.remove(resourceSet); d_ptr->m_resourceSetToReload.remove(resourceSet); d_ptr->m_newlyCreated.remove(resourceSet); delete resourceSet; } void QtResourceModel::reload(const QString &path, int *errorCount, QString *errorMessages) { setModified(path); d_ptr->activate(d_ptr->m_currentResourceSet, d_ptr->m_resourceSetToPaths.value(d_ptr->m_currentResourceSet), errorCount, errorMessages); } void QtResourceModel::reload(int *errorCount, QString *errorMessages) { QMap::iterator it = d_ptr->m_pathToModified.begin(); QMap::iterator itEnd = d_ptr->m_pathToModified.end(); // will it be valid when I iterate the map and change it??? while (it != itEnd) { it = d_ptr->m_pathToModified.insert(it.key(), true); ++it; } QMap::iterator itReload = d_ptr->m_resourceSetToReload.begin(); QMap::iterator itReloadEnd = d_ptr->m_resourceSetToReload.end(); while (itReload != itReloadEnd) { itReload = d_ptr->m_resourceSetToReload.insert(itReload.key(), true); // empty resourceSets could be omitted here ++itReload; } d_ptr->activate(d_ptr->m_currentResourceSet, d_ptr->m_resourceSetToPaths.value(d_ptr->m_currentResourceSet), errorCount, errorMessages); } QMap QtResourceModel::contents() const { return d_ptr->m_fileToQrc; } QString QtResourceModel::qrcPath(const QString &file) const { return d_ptr->m_fileToQrc.value(file); } void QtResourceModel::setWatcherEnabled(bool enable) { if (d_ptr->m_fileWatcherEnabled == enable) return; d_ptr->m_fileWatcherEnabled = enable; QMapIterator it(d_ptr->m_fileWatchedMap); if (it.hasNext()) d_ptr->setWatcherEnabled(it.next().key(), d_ptr->m_fileWatcherEnabled); } bool QtResourceModel::isWatcherEnabled() const { return d_ptr->m_fileWatcherEnabled; } void QtResourceModel::setWatcherEnabled(const QString &path, bool enable) { QMap::Iterator it = d_ptr->m_fileWatchedMap.find(path); if (it == d_ptr->m_fileWatchedMap.end()) return; if (it.value() == enable) return; it.value() = enable; if (!d_ptr->m_fileWatcherEnabled) return; d_ptr->setWatcherEnabled(it.key(), enable); } bool QtResourceModel::isWatcherEnabled(const QString &path) { return d_ptr->m_fileWatchedMap.value(path, false); } QT_END_NAMESPACE #include "moc_qtresourcemodel_p.cpp"