aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Kampas <martin.kampas@jolla.com>2018-10-26 09:57:57 +0200
committerSvetlana Abramenkova <sabramenkova@luxoft.com>2020-03-04 13:30:08 +0000
commitf49c059de342884dc5b93ced4d9c865cd2441309 (patch)
treeb5d88368fd92a43dd8b22eb59ada84acacdd5305
parentb7311851cc2a5f57cc980f277ed8e94f9b576d22 (diff)
Let it work with compiled-in resources
Change-Id: I41e3fc4e9a6916aeec3e61c37cc452bd7325a5d4 Task-number: AUTOSUITE-1098 Reviewed-by: Svetlana Abramenkova <sabramenkova@luxoft.com>
-rw-r--r--src/bench/directorypreviewadapter.cpp10
-rw-r--r--src/bench/hostwidget.cpp10
-rw-r--r--src/fontadapter.cpp7
-rw-r--r--src/imageadapter.cpp3
-rw-r--r--src/livedocument.cpp159
-rw-r--r--src/livedocument.h11
-rw-r--r--src/livenodeengine.cpp93
-rw-r--r--src/livenodeengine.h7
-rw-r--r--src/overlay.cpp13
-rw-r--r--src/overlay.h7
-rw-r--r--src/resourcemap.cpp292
-rw-r--r--src/resourcemap.h66
-rw-r--r--src/src.pri6
13 files changed, 642 insertions, 42 deletions
diff --git a/src/bench/directorypreviewadapter.cpp b/src/bench/directorypreviewadapter.cpp
index e70dabe..856909d 100644
--- a/src/bench/directorypreviewadapter.cpp
+++ b/src/bench/directorypreviewadapter.cpp
@@ -31,6 +31,7 @@
****************************************************************************/
#include "directorypreviewadapter.h"
+#include "livedocument.h"
#include <QFileInfo>
#include <QDir>
#include <QQmlContext>
@@ -64,15 +65,16 @@ bool DirectoryPreviewAdapter::isFullScreen() const
bool DirectoryPreviewAdapter::canAdapt(const QUrl &url) const
{
- QFileInfo info(url.toLocalFile());
- return info.isDir();
+ QString path(LiveDocument::toFilePath(url));
+ return !path.isEmpty() && QFileInfo(path).isDir();
}
QUrl DirectoryPreviewAdapter::adapt(const QUrl &url, QQmlContext *context)
{
- QDir dir(url.toLocalFile());
+ QString path(LiveDocument::toFilePath(url));
+ QDir dir(path);
context->setContextProperty("files", dir.entryList(QDir::Files | QDir::NoDotDot | QDir::NoDot | QDir::NoSymLinks));
- context->setContextProperty("path", url.toLocalFile());
+ context->setContextProperty("path", path);
context->setContextProperty("adapter", this);
if (availableFeatures().testFlag(QtQuickControls))
diff --git a/src/bench/hostwidget.cpp b/src/bench/hostwidget.cpp
index 7ea6781..9ee16f9 100644
--- a/src/bench/hostwidget.cpp
+++ b/src/bench/hostwidget.cpp
@@ -295,6 +295,16 @@ void HostWidget::onConnected()
disconnect(m_connectDisconnectAction, &QAction::triggered, 0, 0);
connect(m_connectDisconnectAction, &QAction::triggered, &m_publisher, &RemotePublisher::disconnectFromServer);
+ // Send .qrc files to let the node fill its resourceMap
+ // TODO not using bulkSend as this could be misinterpreted as a response to
+ // needsPublishWorkspace
+ QDirIterator it(m_engine->workspace(), {"*.qrc"}, QDir::AllEntries | QDir::NoDotAndDotDot,
+ QDirIterator::Subdirectories);
+ while (it.hasNext()) {
+ LiveDocument qrcFile = LiveDocument::resolve(m_engine->workspace(), it.next());
+ sendDocument(qrcFile);
+ }
+
m_publisher.initComplete();
}
diff --git a/src/fontadapter.cpp b/src/fontadapter.cpp
index 7ff3ff0..2592588 100644
--- a/src/fontadapter.cpp
+++ b/src/fontadapter.cpp
@@ -31,6 +31,7 @@
****************************************************************************/
#include "fontadapter.h"
+#include "livedocument.h"
#include <QDebug>
#include <QQmlContext>
@@ -64,7 +65,9 @@ QImage FontAdapter::preview(const QString &path, const QSize &requestedSize)
bool FontAdapter::canAdapt(const QUrl &url) const
{
- QString path = url.toLocalFile();
+ QString path = LiveDocument::toFilePath(url);
+ if (path.isEmpty())
+ return false;
foreach (const QString& extension, fontExtensions) {
if (path.endsWith(extension))
@@ -81,7 +84,7 @@ bool FontAdapter::isFullScreen() const
QUrl FontAdapter::adapt(const QUrl &url, QQmlContext *context)
{
- fontId = base.addApplicationFont(url.toLocalFile());
+ fontId = base.addApplicationFont(LiveDocument::toFilePath(url));
QStringList families = base.applicationFontFamilies(fontId);
diff --git a/src/imageadapter.cpp b/src/imageadapter.cpp
index dca2da6..4844940 100644
--- a/src/imageadapter.cpp
+++ b/src/imageadapter.cpp
@@ -31,6 +31,7 @@
****************************************************************************/
#include "imageadapter.h"
+#include "livedocument.h"
#include <QImageReader>
#include <QDebug>
#include <QFileInfo>
@@ -67,7 +68,7 @@ QImage ImageAdapter::preview(const QString &path, const QSize &requestedSize)
bool ImageAdapter::canAdapt(const QUrl &url) const
{
- return !QImageReader::imageFormat(url.toLocalFile()).isEmpty();
+ return !QImageReader::imageFormat(LiveDocument::toFilePath(url)).isEmpty();
}
QUrl ImageAdapter::adapt(const QUrl &url, QQmlContext *context)
diff --git a/src/livedocument.cpp b/src/livedocument.cpp
index 39a2f39..71730e5 100644
--- a/src/livedocument.cpp
+++ b/src/livedocument.cpp
@@ -31,13 +31,23 @@
****************************************************************************/
#include "livedocument.h"
+#include "resourcemap.h"
#include <QDebug>
/*!
* \class LiveDocument
- * \brief Encapsulates a relative path to a workspace document.
+ * \brief Identifies a workspace document
* \inmodule qmllive
+ *
+ * On the LiveHubEngine side, i.e. where the QmlLive Bench is running, it always
+ * represents a document on file system, under the workspace directory.
+ *
+ * On the LiveNodeEngine side, i.e. on the QmlLive Runtime side, it represents a
+ * document that may exist either on file system or in a Qt resource. In either
+ * case the document is identified by the relative path of the original file
+ * under the workspace directory on the LiveHubEngine side. See
+ * runtimeLocation().
*/
/*!
@@ -81,9 +91,9 @@ LiveDocument::LiveDocument(const QString &relativeFilePath)
/*!
* \fn QString LiveDocument::errorString() const
*
- * When called just after resolve(), existsIn() or isFileIn() failed, returns a
- * descriptive message suitable for displaying in user interface. When called in
- * other context, the result is undefined.
+ * When called just after resolve(), existsIn(), isFileIn() or mapsToResource()
+ * failed, returns a descriptive message suitable for displaying in user
+ * interface. When called in other context, the result is undefined.
*/
/*!
@@ -149,6 +159,54 @@ QString LiveDocument::absoluteFilePathIn(const QDir &workspace) const
}
/*!
+ * Returns \c true if the runtime location of the document is in a Qt resource.
+ * This is determined using the given \a resourceMap.
+ *
+ * \sa runtimeLocation(), LiveNodeEngine::resourceMap(), errorString()
+ */
+bool LiveDocument::mapsToResource(const ResourceMap &resourceMap) const
+{
+ LIVE_ASSERT(!isNull(), return false);
+
+ QString resource = resourceMap.toResource(*this);
+ if (resource.isEmpty()) {
+ m_errorString = tr("Document '%1' does not exist as a Qt resource")
+ .arg(m_relativeFilePath);
+ return false;
+ }
+
+ // TODO Necessary to check if it actually exists? ResourceMap may not be in sync.
+
+ return true;
+}
+
+/*!
+ * Determines the runtime location of the document. On LiveNodeEngine side a
+ * document may exist either on file system (under the given \a workspace) or in
+ * a Qt resource (determined using the given \a resourceMap).
+ *
+ * Example:
+ *
+ * \code
+ * LiveDocument document = ...;
+ * LiveNodeEngine *engine = ...;
+ * QFile file(document.runtimeLocation(engine->workspace(), *engine->resourceMap()));
+ * \endcode
+ *
+ * \sa mapsToResource(), LiveNodeEngine::resourceMap()
+ */
+QUrl LiveDocument::runtimeLocation(const QDir &workspace, const ResourceMap &resourceMap) const
+{
+ LIVE_ASSERT(!isNull(), return QString());
+
+ const QString resource = resourceMap.toResource(*this);
+ if (!resource.isEmpty())
+ return toUrl(resource);
+ else
+ return QUrl::fromLocalFile(absoluteFilePathIn(workspace));
+}
+
+/*!
* Constructs a non-null instance unless the \a filePath resolves outside of the
* \a workspace directory.
*
@@ -182,6 +240,99 @@ LiveDocument LiveDocument::resolve(const QDir &workspace, const QString &filePat
}
/*!
+ * Constructs a non-null instance for \a filePath that either resolves to a path
+ * under the \a workspace directory or determines a Qt resource for which a
+ * mapping exists in the given \a resourceMap.
+ *
+ * \a filePath may be an absolute or relative file path to a file or a Qt
+ * resource path (starting with \c {":/"} ). In either case the file is NOT
+ * required to exist,
+ *
+ * \sa isNull(), errorString()
+ */
+LiveDocument LiveDocument::resolve(const QDir &workspace, const ResourceMap &resourceMap, const QString &filePath)
+{
+ if (filePath.isEmpty()) {
+ qWarning() << "filePath is an empty string";
+ LiveDocument retv;
+ retv.m_errorString = tr("Not a valid file path: ''");
+ return retv;
+ }
+
+ if (filePath.startsWith(":/")) {
+ LiveDocument retv = resourceMap.toDocument(filePath);
+ if (retv.isNull()) {
+ retv.m_errorString = tr("No mapping exists for resource: '%1'").arg(filePath);
+ }
+ return retv;
+ } else {
+ return resolve(workspace, filePath);
+ }
+}
+
+/*!
+ * Constructs a non-null instance for \a fileUrl that either resolves to a path
+ * under the \a workspace directory or determines a Qt resource for which a
+ * mapping exists in the given \a resourceMap.
+ *
+ * \a fileUrl may be an URL representing a local file or a Qt resource path
+ * (e.g. \c {"qrc:/example/icon.png"}). In either case the file is NOT required
+ * to exist,
+ *
+ * \sa isNull(), errorString()
+ */
+LiveDocument LiveDocument::resolve(const QDir &workspace, const ResourceMap &resourceMap, const QUrl &fileUrl)
+{
+ if (fileUrl.isEmpty()) {
+ qWarning() << "fileUrl is an empty URL:" << fileUrl;
+ LiveDocument retv;
+ retv.m_errorString = tr("Not a valid URL: '%1'").arg(fileUrl.toString());
+ return retv;
+ }
+
+ if (fileUrl.scheme() == QLatin1String("qrc")) {
+ LiveDocument retv = resourceMap.toDocument(QLatin1String(":") + fileUrl.path());
+ if (retv.isNull()) {
+ retv.m_errorString = tr("No mapping exists for resource: '%1'").arg(fileUrl.toString());
+ }
+ return retv;
+ } else if (fileUrl.scheme() == QLatin1String("file")) {
+ return resolve(workspace, fileUrl.toLocalFile());
+ } else {
+ qWarning() << "fileUrl is not a supported URL:" << fileUrl;
+ LiveDocument retv;
+ retv.m_errorString = tr("Unsupported URL: '%1'").arg(fileUrl.toString());
+ return retv;
+ }
+}
+
+/*!
+ * Converts a local/Qt resource \a url to file path
+ */
+QString LiveDocument::toFilePath(const QUrl &url)
+{
+ if (url.scheme() == QLatin1String("qrc"))
+ return QLatin1String(":") + url.path();
+ else
+ return url.toLocalFile();
+}
+
+/*!
+ * Converts a local/Qt resource \a filePath to URL
+ */
+QUrl LiveDocument::toUrl(const QString &filePath)
+{
+ if (filePath.startsWith(":/")) {
+ QUrl url;
+ url.setScheme("qrc");
+ url.setPath(filePath.mid(1));
+ return url;
+ } else {
+ return QUrl::fromLocalFile(filePath);
+ }
+}
+
+/*!
* Allows to print LiveDocument \a document via debug stream \a dbg.
*/
QDebug operator<<(QDebug dbg, const LiveDocument &document)
diff --git a/src/livedocument.h b/src/livedocument.h
index b43f49e..0087b0a 100644
--- a/src/livedocument.h
+++ b/src/livedocument.h
@@ -36,6 +36,8 @@
#include "qmllive_global.h"
+class ResourceMap;
+
class QMLLIVESHARED_EXPORT LiveDocument
{
Q_DECLARE_TR_FUNCTIONS(LiveDocument)
@@ -53,7 +55,16 @@ public:
QString relativeFilePath() const;
QString absoluteFilePathIn(const QDir &workspace) const;
+ bool mapsToResource(const ResourceMap &resourceMap) const;
+ QUrl runtimeLocation(const QDir &workspace, const ResourceMap &resourceMap) const;
+
static LiveDocument resolve(const QDir &workspace, const QString &filePath);
+ static LiveDocument resolve(const QDir &workspace, const ResourceMap &resourceMap, const QString
+ &filePath);
+ static LiveDocument resolve(const QDir &workspace, const ResourceMap &resourceMap, const QUrl &fileUrl);
+
+ static QString toFilePath(const QUrl &url);
+ static QUrl toUrl(const QString &filePath);
friend inline bool operator==(const LiveDocument &d1, const LiveDocument &d2)
{ return d1.m_relativeFilePath == d2.m_relativeFilePath; }
diff --git a/src/livenodeengine.cpp b/src/livenodeengine.cpp
index dbbea10..8f1ff0f 100644
--- a/src/livenodeengine.cpp
+++ b/src/livenodeengine.cpp
@@ -33,6 +33,7 @@
#include "livenodeengine.h"
#include "liveruntime.h"
#include "qmlhelper.h"
+#include "resourcemap.h"
#include "contentpluginfactory.h"
#include "imageadapter.h"
#include "fontadapter.h"
@@ -48,6 +49,11 @@
#define DEBUG if (0) qDebug()
#endif
+namespace {
+const char *const OVERLAY_PATH_PREFIX = "qml-live-overlay--";
+const char OVERLAY_PATH_SEPARATOR = '-';
+}
+
/*!
* \class LiveNodeEngine
* \brief The LiveNodeEngine class instantiates QML components in cooperation with LiveHubEngine.
@@ -83,7 +89,10 @@
* With this option enabled, updates can be received even if workspace
* is read only. Updates will be stored in a writable overlay stacked
* over the original workspace with the help of
- * QQmlAbstractUrlInterceptor. Requires \l AllowUpdates.
+ * QQmlAbstractUrlInterceptor. This option only influences the way how
+ * updates to file system files are handled - updates to Qt resource
+ * files are always stored in an overlay.
+ * Requires \l AllowUpdates.
* \value AllowCreateMissing
* Without this option enabled, updates are only accepted for existing
* workspace documents. Requires \l AllowUpdates.
@@ -91,17 +100,21 @@
* \sa {QML Live Runtime}
*/
-class OverlayUrlInterceptor : public QObject, public QQmlAbstractUrlInterceptor
+class UrlInterceptor : public QObject, public QQmlAbstractUrlInterceptor
{
Q_OBJECT
public:
- OverlayUrlInterceptor(const Overlay *overlay, QQmlAbstractUrlInterceptor *otherInterceptor, QObject *parent)
+ UrlInterceptor(const QDir &workspace, const Overlay *overlay, const ResourceMap *resourceMap,
+ QQmlAbstractUrlInterceptor *otherInterceptor, QObject *parent)
: QObject(parent)
, m_otherInterceptor(otherInterceptor)
+ , m_workspace(workspace)
, m_overlay(overlay)
+ , m_resourceMap(resourceMap)
{
Q_ASSERT(overlay);
+ Q_ASSERT(resourceMap);
}
// From QQmlAbstractUrlInterceptor
@@ -109,7 +122,20 @@ public:
{
const QUrl url_ = m_otherInterceptor ? m_otherInterceptor->intercept(url, type) : url;
if (url_.scheme() == QLatin1String("file")) {
- return QUrl::fromLocalFile(m_overlay->map(url_.toLocalFile()));
+ bool existingOnly = true;
+ return QUrl::fromLocalFile(m_overlay->map(url_.toLocalFile(), existingOnly));
+ } else if (url_.scheme() == QLatin1String("qrc")) {
+ const LiveDocument document = LiveDocument::resolve(m_workspace, *m_resourceMap, url_);
+ if (document.isNull())
+ return url_;
+
+ QString filePath = document.absoluteFilePathIn(m_workspace);
+ bool existingOnly = false;
+ filePath = m_overlay->map(filePath, existingOnly);
+ if (!QFileInfo(filePath).exists())
+ return url_;
+
+ return QUrl::fromLocalFile(filePath);
} else {
return url_;
}
@@ -117,7 +143,9 @@ public:
private:
QQmlAbstractUrlInterceptor *m_otherInterceptor;
- QPointer<const Overlay> m_overlay;
+ const QDir m_workspace;
+ const QPointer<const Overlay> m_overlay;
+ const QPointer<const ResourceMap> m_resourceMap;
};
/*!
@@ -129,6 +157,7 @@ LiveNodeEngine::LiveNodeEngine(QObject *parent)
, m_xOffset(0)
, m_yOffset(0)
, m_rotation(0)
+ , m_resourceMap(new ResourceMap(this))
, m_delayReload(new QTimer(this))
, m_pluginFactory(new ContentPluginFactory(this))
, m_activePlugin(0)
@@ -276,10 +305,11 @@ void LiveNodeEngine::usePreloadedDocument(const LiveDocument &document, QObject
m_activeFile = document;
- if (!m_activeFile.existsIn(m_workspace)) {
+ if (!m_activeFile.existsIn(m_workspace) && !m_activeFile.mapsToResource(*m_resourceMap)) {
QQmlError error;
error.setUrl(QUrl::fromLocalFile(m_activeFile.absoluteFilePathIn(m_workspace)));
- error.setDescription(tr("File not found"));
+ error.setDescription(tr("File not found under the workspace "
+ "and no mapping to a Qt resource exists for that file"));
emit logErrors(QList<QQmlError>() << error);
}
@@ -312,7 +342,7 @@ void LiveNodeEngine::usePreloadedDocument(const QString &document, QQuickWindow
{
LIVE_ASSERT(m_activeFile.isNull(), return);
- LiveDocument resolved = LiveDocument::resolve(workspace(), document);
+ LiveDocument resolved = LiveDocument::resolve(workspace(), *m_resourceMap, document);
if (resolved.isNull()) {
qWarning() << "Failed to resolve preloaded document path:" << document
<< "Workspace: " << workspace();
@@ -411,9 +441,13 @@ void LiveNodeEngine::reloadDocument()
emit clearLog();
- const QUrl originalUrl = QUrl::fromLocalFile(m_activeFile.absoluteFilePathIn(m_workspace));
+ const QUrl originalUrl = m_activeFile.runtimeLocation(m_workspace, *m_resourceMap);
const QUrl url = queryDocumentViewer(originalUrl);
+ DEBUG << "Loading document" << m_activeFile << "runtime location:" << originalUrl;
+ if (url != originalUrl)
+ DEBUG << "Using viewer" << url;
+
auto showErrorScreen = [this] {
Q_ASSERT(m_fallbackView);
m_fallbackView->setResizeMode(QQuickView::SizeRootObjectToView);
@@ -505,20 +539,28 @@ void LiveNodeEngine::reloadDocument()
*/
void LiveNodeEngine::updateDocument(const LiveDocument &document, const QByteArray &content)
{
+ if (QFileInfo(document.relativeFilePath()).suffix() == QLatin1String("qrc")) {
+ QBuffer buffer;
+ buffer.setData(content);
+ buffer.open(QIODevice::ReadOnly);
+ if (!m_resourceMap->updateMapping(document, &buffer))
+ qWarning() << "Unable to parse qrc file " << document.relativeFilePath() << ":" << m_resourceMap->errorString();
+ }
+
if (!(m_workspaceOptions & AllowUpdates)) {
return;
}
- QString documentPath = document.absoluteFilePathIn(m_workspace);
+ bool existsInWorkspace = document.existsIn(m_workspace);
+ bool mapsToResource = document.mapsToResource(*m_resourceMap);
+ if (!existsInWorkspace && !mapsToResource && !(m_workspaceOptions & AllowCreateMissing))
+ return;
- if (!(m_workspaceOptions & AllowCreateMissing)) {
- if (!QFileInfo(documentPath).exists())
- return;
- }
+ bool useOverlay = (m_workspaceOptions & UpdatesAsOverlay) || mapsToResource;
- QString writablePath = (m_workspaceOptions & UpdatesAsOverlay)
- ? m_overlay->reserve(document)
- : documentPath;
+ QString writablePath = useOverlay
+ ? m_overlay->reserve(document, existsInWorkspace)
+ : document.absoluteFilePathIn(m_workspace);
QString writableDirPath = QFileInfo(writablePath).absoluteDir().absolutePath();
QDir().mkpath(writableDirPath);
@@ -592,16 +634,27 @@ void LiveNodeEngine::setWorkspace(const QString &path, WorkspaceOptions options)
m_workspaceOptions |= AllowUpdates;
}
- if (m_workspaceOptions & UpdatesAsOverlay) {
+ if (m_workspaceOptions & AllowUpdates) {
+ // Even without UpdatesAsOverlay the overlay is used for Qt resources
m_overlay = new Overlay(m_workspace.path(), this);
- m_overlayUrlInterceptor = new OverlayUrlInterceptor(m_overlay, qmlEngine()->urlInterceptor(), this);
- qmlEngine()->setUrlInterceptor(m_overlayUrlInterceptor);
+ m_urlInterceptor = new UrlInterceptor(m_workspace, m_overlay, m_resourceMap, qmlEngine()->urlInterceptor(), this);
+ qmlEngine()->setUrlInterceptor(m_urlInterceptor);
}
emit workspaceChanged(workspace());
}
/*!
+ * Returns the ResourceMap managed by this instance.
+ *
+ * \sa LiveDocument
+ */
+ResourceMap *LiveNodeEngine::resourceMap() const
+{
+ return m_resourceMap;
+}
+
+/*!
* Sets the pluginPath to \a path.
*
* The pluginPath will be used to load QML Live plugins
diff --git a/src/livenodeengine.h b/src/livenodeengine.h
index 8594084..17c5cf9 100644
--- a/src/livenodeengine.h
+++ b/src/livenodeengine.h
@@ -42,7 +42,8 @@
class LiveRuntime;
class ContentPluginFactory;
-class OverlayUrlInterceptor;
+class ResourceMap;
+class UrlInterceptor;
class QMLLIVESHARED_EXPORT LiveNodeEngine : public QObject
{
@@ -79,6 +80,7 @@ public:
QString workspace() const;
void setWorkspace(const QString &path, WorkspaceOptions options = NoWorkspaceOption);
+ ResourceMap *resourceMap() const;
void setPluginPath(const QString& path);
QString pluginPath() const;
@@ -136,8 +138,9 @@ private:
QList<QMetaObject::Connection> m_activeWindowConnections;
QDir m_workspace;
WorkspaceOptions m_workspaceOptions;
- QPointer<OverlayUrlInterceptor> m_overlayUrlInterceptor;
+ QPointer<UrlInterceptor> m_urlInterceptor;
QPointer<Overlay> m_overlay;
+ QPointer<ResourceMap> m_resourceMap;
QTimer *m_delayReload;
ContentPluginFactory* m_pluginFactory;
diff --git a/src/overlay.cpp b/src/overlay.cpp
index 92f890c..a97e1d7 100644
--- a/src/overlay.cpp
+++ b/src/overlay.cpp
@@ -60,18 +60,23 @@ QDir Overlay::overlay() const
return m_overlay.path();
}
-QString Overlay::reserve(const LiveDocument &document)
+QString Overlay::reserve(const LiveDocument &document, bool existing)
{
QWriteLocker locker(&m_lock);
QString overlayingPath = document.absoluteFilePathIn(m_overlay.path());
- m_mappings.insert(document.absoluteFilePathIn(m_basePath), overlayingPath);
+ m_mappings.insert(document.absoluteFilePathIn(m_basePath), qMakePair(overlayingPath, existing));
return overlayingPath;
}
-QString Overlay::map(const QString &file) const
+QString Overlay::map(const QString &file, bool existingOnly) const
{
QReadLocker locker(&m_lock);
- return m_mappings.value(file, file);
+ auto it = m_mappings.find(file);
+ if (it == m_mappings.end())
+ return file;
+ if (existingOnly && !it->second)
+ return file;
+ return it->first;
}
QString Overlay::overlayTemplatePath()
diff --git a/src/overlay.h b/src/overlay.h
index 92f609a..48d80e0 100644
--- a/src/overlay.h
+++ b/src/overlay.h
@@ -45,16 +45,17 @@ public:
Overlay(const QString &basePath, QObject *parent);
~Overlay();
- QString reserve(const LiveDocument &document);
+ QString reserve(const LiveDocument &document, bool existing);
QDir overlay() const;
- QString map(const QString &file) const;
+ QString map(const QString &file, bool existingOnly) const;
private:
static QString overlayTemplatePath();
private:
mutable QReadWriteLock m_lock;
- QHash<QString, QString> m_mappings;
+ // base path -> ( overlaying path, file exists at base path )
+ QHash<QString, QPair<QString, bool>> m_mappings;
QString m_basePath;
QTemporaryDir m_overlay;
};
diff --git a/src/resourcemap.cpp b/src/resourcemap.cpp
new file mode 100644
index 0000000..3d8f80b
--- /dev/null
+++ b/src/resourcemap.cpp
@@ -0,0 +1,292 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 Jolla Ltd
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QmlLive tool.
+**
+** $QT_BEGIN_LICENSE:GPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite licenses may use
+** this file in accordance with the commercial license agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and The Qt Company. For
+** licensing terms and conditions see https://www.qt.io/terms-conditions.
+** For further information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+** SPDX-License-Identifier: GPL-3.0
+**
+****************************************************************************/
+
+#include "resourcemap.h"
+
+#include "livedocument.h"
+
+Q_DECLARE_LOGGING_CATEGORY(rmLog)
+Q_LOGGING_CATEGORY(rmLog, "QmlLive.ResourceMap", QtInfoMsg)
+
+/*!
+ * \class ResourceMap
+ * \brief Maps compiled-in Qt resources to workspace documents and vice versa
+ * \inmodule qmllive
+ *
+ * A ResourceMap is managed and owned by a LiveNodeEngine. LiveDocument uses
+ * ResourceMap to enable convenient handling of documents that may reside
+ * either under workspace on file system or in Qt resources at run time.
+ *
+ * \sa LiveNodeEngine::resourceMap(), LiveDocument
+ */
+
+class QrcReader : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit QrcReader(QObject *parent = nullptr)
+ : QObject(parent)
+ {
+ }
+
+ bool read(QIODevice *input, QString *errorString)
+ {
+ Q_ASSERT(errorString);
+
+ m_xml.setDevice(input);
+
+ if (m_xml.readNextStartElement()) {
+ if (m_xml.name() == QLatin1String("RCC")
+ && (!m_xml.attributes().hasAttribute(QLatin1String("version"))
+ || m_xml.attributes().value(QLatin1String("version")) == QLatin1String("1.0"))) {
+ readRCC();
+ } else {
+ m_xml.raiseError(QObject::tr("The file is not an RCC (version 1.0) file."));
+ }
+ }
+
+ if (m_xml.error())
+ *errorString = m_xml.errorString();
+
+ return !m_xml.error();
+ }
+
+signals:
+ void fileRead(const QString &resource, const QString &file,
+ QLocale::Language language, QLocale::Country country);
+
+private:
+ void readRCC()
+ {
+ Q_ASSERT(m_xml.isStartElement() && m_xml.name() == QLatin1String("RCC"));
+
+ while (m_xml.readNextStartElement()) {
+ if (m_xml.name() == QLatin1String("qresource"))
+ readQresource();
+ else
+ raiseUnexpectedTagError();
+ }
+ }
+
+ void readQresource()
+ {
+ Q_ASSERT(m_xml.isStartElement() && m_xml.name() == QLatin1String("qresource"));
+
+ m_prefix = m_xml.attributes().value(QLatin1String("prefix")).toString();
+
+ m_language = QLocale::c().language();
+ m_country = QLocale::c().country();
+ QString lang = m_xml.attributes().value(QLatin1String("lang")).toString();
+ if (!lang.isEmpty()) {
+ QLocale locale = QLocale(lang);
+ m_language = locale.language();
+ m_country = lang.length() > 2 ? locale.country() : QLocale::AnyCountry;
+ }
+
+ while (m_xml.readNextStartElement()) {
+ if (m_xml.name() == QLatin1String("file"))
+ readFile();
+ else
+ raiseUnexpectedTagError();
+ }
+ }
+
+ void readFile()
+ {
+ Q_ASSERT(m_xml.isStartElement() && m_xml.name() == QLatin1String("file"));
+
+ QString resource = m_xml.attributes().value(QLatin1String("alias")).toString();
+ QString file = m_xml.readElementText();
+
+ if (resource.isEmpty())
+ resource = file;
+ if (!m_prefix.isEmpty())
+ resource = m_prefix + QLatin1Char('/') + resource;
+ if (resource.at(0) != QLatin1Char('/'))
+ resource.prepend(QLatin1String(":/"));
+ else
+ resource.prepend(QLatin1Char(':'));
+
+ emit fileRead(resource, file, m_language, m_country);
+ }
+
+ void raiseUnexpectedTagError()
+ {
+ m_xml.raiseError(QStringLiteral("Unexpected tag %1").arg(m_xml.name().toString()));
+ }
+
+private:
+ QXmlStreamReader m_xml;
+ QString m_prefix;
+ QLocale::Language m_language{QLocale::AnyLanguage};
+ QLocale::Country m_country{QLocale::AnyCountry};
+};
+
+/*!
+ * Constructs with the given \a parent object.
+ */
+ResourceMap::ResourceMap(QObject *parent)
+ : QObject(parent)
+{
+}
+
+/*!
+ * Returns the resource that maps to the given \a document or an empty QString
+ * if no corresponding mapping exists.
+ *
+ * This is a low level API. LiveDocument provides more convenient API.
+ */
+QString ResourceMap::toResource(const LiveDocument &document) const
+{
+ LIVE_ASSERT(!document.isNull(), return QString());
+
+ QReadLocker locker(&m_lock);
+ auto localizedIt = m_resourcesByDocument.find(systemLocalePrefix() + document.relativeFilePath());
+ if (localizedIt != m_resourcesByDocument.end())
+ return *localizedIt;
+ auto nonlocalizedIt = m_resourcesByDocument.find(cLocalePrefix() + document.relativeFilePath());
+ if (nonlocalizedIt != m_resourcesByDocument.end())
+ return *nonlocalizedIt;
+ return QString();
+}
+
+/*!
+ * Returns the document that maps to the given \a resource or a null
+ * LiveDocument if no corresponding mapping exists.
+ *
+ * This is a low level API. LiveDocument provides more convenient API.
+ */
+LiveDocument ResourceMap::toDocument(const QString &resource) const
+{
+ LIVE_ASSERT(!resource.isEmpty(), return LiveDocument());
+
+ QReadLocker locker(&m_lock);
+ auto localizedIt = m_documentByResource.find(systemLocalePrefix() + resource);
+ if (localizedIt != m_documentByResource.end())
+ return LiveDocument(*localizedIt);
+ auto nonlocalizedIt = m_documentByResource.find(cLocalePrefix() + resource);
+ if (nonlocalizedIt != m_documentByResource.end())
+ return LiveDocument(*nonlocalizedIt);
+ return LiveDocument();
+}
+
+/*!
+ * Returns an user readable description of the last error occurred.
+ *
+ * The error string may be set by the updateMapping() call.
+ */
+QString ResourceMap::errorString() const
+{
+ return m_errorString;
+}
+
+/*!
+ * Updates mapping from the given \c .qrc document
+ *
+ * Old mappings for the given \a qrcDocument will be removed, then new mappings
+ * will be added based on the content of the \a qrcFile.
+ *
+ * Returns \c true on success. Otherwise errorString() is set and \c false
+ * returned.
+ */
+bool ResourceMap::updateMapping(const LiveDocument &qrcDocument, QIODevice *qrcFile)
+{
+ LIVE_ASSERT(!qrcDocument.isNull(), return false);
+ LIVE_ASSERT(qrcFile, return false);
+ LIVE_ASSERT(qrcFile->openMode() & QIODevice::ReadOnly, return false);
+
+ qCDebug(rmLog) << "Updating resource map for" << qrcDocument;
+
+ const QString qrcPath = QFileInfo(qrcDocument.relativeFilePath()).path();
+
+ QWriteLocker locker(&m_lock);
+
+ removeMapping(qrcDocument);
+
+ auto addMapping = [&](const QString &resource, const QString &file,
+ QLocale::Language language, QLocale::Country country) {
+ QString localePrefix = toLocalePrefix(language, country);
+ QString prefixedResourceName = localePrefix + resource;
+ QString filePath = QDir::cleanPath(QFileInfo(qrcPath, file).filePath());
+
+ // The bench sends all .qrc files found in the workspace, not just those actually built-in
+ if (!QFileInfo(resource).exists()) {
+ qCDebug(rmLog) << "Not mapping" << prefixedResourceName << "to" << filePath << "(resource does not exist)";
+ return;
+ }
+ qCDebug(rmLog) << "Mapping" << prefixedResourceName << "to" << filePath;
+
+ m_resourcesByDocument.insertMulti(localePrefix + filePath, resource);
+ m_documentByResource.insert(prefixedResourceName, filePath);
+ m_resourcesByQrc.insert(qrcDocument.relativeFilePath(), prefixedResourceName);
+ };
+
+ QrcReader reader;
+ connect(&reader, &QrcReader::fileRead, addMapping);
+
+ bool ok = reader.read(qrcFile, &m_errorString);
+ if (!ok)
+ removeMapping(qrcDocument);
+
+ return ok;
+}
+
+void ResourceMap::removeMapping(const LiveDocument &qrcDocument)
+{
+ LIVE_ASSERT(!qrcDocument.isNull(), return);
+
+ auto it = m_resourcesByQrc.find(qrcDocument.relativeFilePath());
+ while (it != m_resourcesByQrc.end() && it.key() == qrcDocument.relativeFilePath()) {
+ QString localePrefix = it->left(it->indexOf(QLatin1Char('/')));
+ QString resource = it->mid(localePrefix.length() + 1);
+ QString file = m_documentByResource.take(*it);
+ m_resourcesByDocument.remove(localePrefix + file, resource);
+ it = m_resourcesByQrc.erase(it);
+ }
+}
+
+QString ResourceMap::toLocalePrefix(QLocale::Language language, QLocale::Country country)
+{
+ return QLocale(language, country).name() + QLatin1Char('/');
+}
+
+QString ResourceMap::cLocalePrefix()
+{
+ return QLocale::c().name() + QLatin1Char('/');
+}
+
+QString ResourceMap::systemLocalePrefix()
+{
+ return QLocale::system().name() + QLatin1Char('/');
+}
+
+#include "resourcemap.moc"
diff --git a/src/resourcemap.h b/src/resourcemap.h
new file mode 100644
index 0000000..11726f6
--- /dev/null
+++ b/src/resourcemap.h
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 Jolla Ltd
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QmlLive tool.
+**
+** $QT_BEGIN_LICENSE:GPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite licenses may use
+** this file in accordance with the commercial license agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and The Qt Company. For
+** licensing terms and conditions see https://www.qt.io/terms-conditions.
+** For further information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+** SPDX-License-Identifier: GPL-3.0
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QtCore>
+
+#include "qmllive_global.h"
+
+class LiveDocument;
+
+class QMLLIVESHARED_EXPORT ResourceMap : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit ResourceMap(QObject *parent = nullptr);
+
+ QString toResource(const LiveDocument &document) const;
+ LiveDocument toDocument(const QString &resourceName) const;
+
+ QString errorString() const;
+
+ bool updateMapping(const LiveDocument &qrcDocument, QIODevice *qrcFile);
+
+private:
+ void removeMapping(const LiveDocument &qrcDocument);
+ static QString toLocalePrefix(QLocale::Language language, QLocale::Country country);
+ static QString cLocalePrefix();
+ static QString systemLocalePrefix();
+
+private:
+ mutable QReadWriteLock m_lock;
+ QString m_errorString;
+ QMultiHash<QString, QString> m_resourcesByDocument;
+ QHash<QString, QString> m_documentByResource;
+ QMultiHash<QString, QString> m_resourcesByQrc;
+};
diff --git a/src/src.pri b/src/src.pri
index e730078..dc7c5ea 100644
--- a/src/src.pri
+++ b/src/src.pri
@@ -23,7 +23,8 @@ SOURCES += \
$$PWD/remotelogger.cpp \
$$PWD/logreceiver.cpp \
$$PWD/fontadapter.cpp \
- $$PWD/projectmanager.cpp
+ $$PWD/projectmanager.cpp \
+ $$PWD/resourcemap.cpp
public_headers += \
$$PWD/livedocument.h \
@@ -37,7 +38,8 @@ public_headers += \
$$PWD/remotereceiver.h \
$$PWD/contentadapterinterface.h \
$$PWD/remotelogger.h \
- $$PWD/projectmanager.h
+ $$PWD/projectmanager.h \
+ $$PWD/resourcemap.h
HEADERS += \
$$PWD/constants.h \