aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/qml/qqmltypeloader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qml/qml/qqmltypeloader.cpp')
-rw-r--r--src/qml/qml/qqmltypeloader.cpp1928
1 files changed, 1928 insertions, 0 deletions
diff --git a/src/qml/qml/qqmltypeloader.cpp b/src/qml/qml/qqmltypeloader.cpp
new file mode 100644
index 0000000000..cde2885f0c
--- /dev/null
+++ b/src/qml/qml/qqmltypeloader.cpp
@@ -0,0 +1,1928 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtQml module 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 "qqmltypeloader_p.h"
+
+#include <private/qqmlengine_p.h>
+#include <private/qqmlglobal_p.h>
+#include <private/qqmlthread_p.h>
+#include <private/qqmlcompiler_p.h>
+#include <private/qqmlcomponent_p.h>
+#include <private/qqmlprofilerservice_p.h>
+
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qmutex.h>
+#include <QtCore/qthread.h>
+#include <QtCore/qdiriterator.h>
+#include <QtCore/qwaitcondition.h>
+#include <QtQml/qqmlcomponent.h>
+#include <QtQml/qqmlextensioninterface.h>
+
+#if defined (Q_OS_UNIX)
+#include <sys/types.h>
+#include <dirent.h>
+#endif
+
+// #define DATABLOB_DEBUG
+
+#ifdef DATABLOB_DEBUG
+
+#define ASSERT_MAINTHREAD() do { if(m_thread->isThisThread()) qFatal("QQmlDataLoader: Caller not in main thread"); } while(false)
+#define ASSERT_LOADTHREAD() do { if(!m_thread->isThisThread()) qFatal("QQmlDataLoader: Caller not in load thread"); } while(false)
+#define ASSERT_CALLBACK() do { if(!m_manager || !m_manager->m_thread->isThisThread()) qFatal("QQmlDataBlob: An API call was made outside a callback"); } while(false)
+
+#else
+
+#define ASSERT_MAINTHREAD()
+#define ASSERT_LOADTHREAD()
+#define ASSERT_CALLBACK()
+
+#endif
+
+QT_BEGIN_NAMESPACE
+
+// This is a lame object that we need to ensure that slots connected to
+// QNetworkReply get called in the correct thread (the loader thread).
+// As QQmlDataLoader lives in the main thread, and we can't use
+// Qt::DirectConnection connections from a QNetworkReply (because then
+// sender() wont work), we need to insert this object in the middle.
+class QQmlDataLoaderNetworkReplyProxy : public QObject
+{
+ Q_OBJECT
+public:
+ QQmlDataLoaderNetworkReplyProxy(QQmlDataLoader *l);
+
+public slots:
+ void finished();
+ void downloadProgress(qint64, qint64);
+
+private:
+ QQmlDataLoader *l;
+};
+
+class QQmlDataLoaderThread : public QQmlThread
+{
+ typedef QQmlDataLoaderThread This;
+
+public:
+ QQmlDataLoaderThread(QQmlDataLoader *loader);
+ QNetworkAccessManager *networkAccessManager() const;
+ QQmlDataLoaderNetworkReplyProxy *networkReplyProxy() const;
+
+ void load(QQmlDataBlob *b);
+ void loadAsync(QQmlDataBlob *b);
+ void loadWithStaticData(QQmlDataBlob *b, const QByteArray &);
+ void loadWithStaticDataAsync(QQmlDataBlob *b, const QByteArray &);
+ void callCompleted(QQmlDataBlob *b);
+ void callDownloadProgressChanged(QQmlDataBlob *b, qreal p);
+ void initializeEngine(QQmlExtensionInterface *, const char *);
+
+protected:
+ virtual void shutdownThread();
+
+private:
+ void loadThread(QQmlDataBlob *b);
+ void loadWithStaticDataThread(QQmlDataBlob *b, const QByteArray &);
+ void callCompletedMain(QQmlDataBlob *b);
+ void callDownloadProgressChangedMain(QQmlDataBlob *b, qreal p);
+ void initializeEngineMain(QQmlExtensionInterface *iface, const char *uri);
+
+ QQmlDataLoader *m_loader;
+ mutable QNetworkAccessManager *m_networkAccessManager;
+ mutable QQmlDataLoaderNetworkReplyProxy *m_networkReplyProxy;
+};
+
+
+QQmlDataLoaderNetworkReplyProxy::QQmlDataLoaderNetworkReplyProxy(QQmlDataLoader *l)
+: l(l)
+{
+}
+
+void QQmlDataLoaderNetworkReplyProxy::finished()
+{
+ Q_ASSERT(sender());
+ Q_ASSERT(qobject_cast<QNetworkReply *>(sender()));
+ QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
+ l->networkReplyFinished(reply);
+}
+
+void QQmlDataLoaderNetworkReplyProxy::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
+{
+ Q_ASSERT(sender());
+ Q_ASSERT(qobject_cast<QNetworkReply *>(sender()));
+ QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
+ l->networkReplyProgress(reply, bytesReceived, bytesTotal);
+}
+
+/*
+Returns the set of QML files in path (qmldir, *.qml, *.js). The caller
+is responsible for deleting the returned data.
+Returns 0 if the directory does not exist.
+*/
+#if defined (Q_OS_UNIX) && !defined(Q_OS_DARWIN)
+static QStringHash<bool> *qmlFilesInDirectory(const QString &path)
+{
+ QByteArray name(QFile::encodeName(path));
+ DIR *dd = opendir(name);
+ if (!dd)
+ return 0;
+
+ struct dirent *result;
+ union {
+ struct dirent d;
+ char b[offsetof (struct dirent, d_name) + NAME_MAX + 1];
+ } u;
+
+ QStringHash<bool> *files = new QStringHash<bool>;
+ while (readdir_r(dd, &u.d, &result) == 0 && result != 0) {
+ if (!strcmp(u.d.d_name, "qmldir")) {
+ files->insert(QLatin1String("qmldir"), true);
+ continue;
+ }
+ int len = strlen(u.d.d_name);
+ if (len < 4)
+ continue;
+ if (!strcmp(u.d.d_name+len-4, ".qml") || !strcmp(u.d.d_name+len-3, ".js"))
+ files->insert(QFile::decodeName(u.d.d_name), true);
+#if defined(Q_OS_DARWIN)
+ else if ((len > 6 && !strcmp(u.d.d_name+len-6, ".dylib")) || !strcmp(u.d.d_name+len-3, ".so")
+ || (len > 7 && !strcmp(u.d.d_name+len-7, ".bundle")))
+ files->insert(QFile::decodeName(u.d.d_name), true);
+#else // Unix
+ else if (!strcmp(u.d.d_name+len-3, ".so") || !strcmp(u.d.d_name+len-3, ".sl"))
+ files->insert(QFile::decodeName(u.d.d_name), true);
+#endif
+ }
+
+ closedir(dd);
+ return files;
+}
+#else
+static QStringHash<bool> *qmlFilesInDirectory(const QString &path)
+{
+ QDirIterator dir(path, QDir::Files);
+ if (!dir.hasNext())
+ return 0;
+ QStringHash<bool> *files = new QStringHash<bool>;
+ while (dir.hasNext()) {
+ dir.next();
+ QString fileName = dir.fileName();
+ if (fileName == QLatin1String("qmldir")
+ || fileName.endsWith(QLatin1String(".qml"))
+ || fileName.endsWith(QLatin1String(".js"))
+#if defined(Q_OS_WIN32) || defined(Q_OS_WINCE)
+ || fileName.endsWith(QLatin1String(".dll"))
+#elif defined(Q_OS_DARWIN)
+ || fileName.endsWith(QLatin1String(".dylib"))
+ || fileName.endsWith(QLatin1String(".so"))
+ || fileName.endsWith(QLatin1String(".bundle"))
+#else // Unix
+ || fileName.endsWith(QLatin1String(".so"))
+ || fileName.endsWith(QLatin1String(".sl"))
+#endif
+ ) {
+#if defined(Q_OS_WIN32) || defined(Q_OS_WINCE) || defined(Q_OS_DARWIN)
+ fileName = fileName.toLower();
+#endif
+ files->insert(fileName, true);
+ }
+ }
+ return files;
+}
+#endif
+
+
+/*!
+\class QQmlDataBlob
+\brief The QQmlDataBlob encapsulates a data request that can be issued to a QQmlDataLoader.
+\internal
+
+QQmlDataBlob's are loaded by a QQmlDataLoader. The user creates the QQmlDataBlob
+and then calls QQmlDataLoader::load() or QQmlDataLoader::loadWithStaticData() to load it.
+The QQmlDataLoader invokes callbacks on the QQmlDataBlob as data becomes available.
+*/
+
+/*!
+\enum QQmlDataBlob::Status
+
+This enum describes the status of the data blob.
+
+\list
+\o Null The blob has not yet been loaded by a QQmlDataLoader
+\o Loading The blob is loading network data. The QQmlDataBlob::setData() callback has not yet been
+invoked or has not yet returned.
+\o WaitingForDependencies The blob is waiting for dependencies to be done before continueing. This status
+only occurs after the QQmlDataBlob::setData() callback has been made, and when the blob has outstanding
+dependencies.
+\o Complete The blob's data has been loaded and all dependencies are done.
+\o Error An error has been set on this blob.
+\endlist
+*/
+
+/*!
+\enum QQmlDataBlob::Type
+
+This enum describes the type of the data blob.
+
+\list
+\o QmlFile This is a QQmlTypeData
+\o JavaScriptFile This is a QQmlScriptData
+\o QmldirFile This is a QQmlQmldirData
+\endlist
+*/
+
+/*!
+Create a new QQmlDataBlob for \a url and of the provided \a type.
+*/
+QQmlDataBlob::QQmlDataBlob(const QUrl &url, Type type)
+: m_type(type), m_url(url), m_finalUrl(url), m_manager(0), m_redirectCount(0),
+ m_inCallback(false), m_isDone(false)
+{
+}
+
+/*! \internal */
+QQmlDataBlob::~QQmlDataBlob()
+{
+ Q_ASSERT(m_waitingOnMe.isEmpty());
+
+ cancelAllWaitingFor();
+}
+
+/*!
+Returns the type provided to the constructor.
+*/
+QQmlDataBlob::Type QQmlDataBlob::type() const
+{
+ return m_type;
+}
+
+/*!
+Returns the blob's status.
+*/
+QQmlDataBlob::Status QQmlDataBlob::status() const
+{
+ return m_data.status();
+}
+
+/*!
+Returns true if the status is Null.
+*/
+bool QQmlDataBlob::isNull() const
+{
+ return status() == Null;
+}
+
+/*!
+Returns true if the status is Loading.
+*/
+bool QQmlDataBlob::isLoading() const
+{
+ return status() == Loading;
+}
+
+/*!
+Returns true if the status is WaitingForDependencies.
+*/
+bool QQmlDataBlob::isWaiting() const
+{
+ return status() == WaitingForDependencies;
+}
+
+/*!
+Returns true if the status is Complete.
+*/
+bool QQmlDataBlob::isComplete() const
+{
+ return status() == Complete;
+}
+
+/*!
+Returns true if the status is Error.
+*/
+bool QQmlDataBlob::isError() const
+{
+ return status() == Error;
+}
+
+/*!
+Returns true if the status is Complete or Error.
+*/
+bool QQmlDataBlob::isCompleteOrError() const
+{
+ Status s = status();
+ return s == Error || s == Complete;
+}
+
+/*!
+Returns the data download progress from 0 to 1.
+*/
+qreal QQmlDataBlob::progress() const
+{
+ quint8 p = m_data.progress();
+ if (p == 0xFF) return 1.;
+ else return qreal(p) / qreal(0xFF);
+}
+
+/*!
+Returns the blob url passed to the constructor. If a network redirect
+happens while fetching the data, this url remains the same.
+
+\sa finalUrl()
+*/
+QUrl QQmlDataBlob::url() const
+{
+ return m_url;
+}
+
+/*!
+Returns the final url of the data. Initially this is the same as
+url(), but if a network redirect happens while fetching the data, this url
+is updated to reflect the new location.
+
+May only be called from the load thread, or after the blob isCompleteOrError().
+*/
+QUrl QQmlDataBlob::finalUrl() const
+{
+ Q_ASSERT(isCompleteOrError() || (m_manager && m_manager->m_thread->isThisThread()));
+ return m_finalUrl;
+}
+
+/*!
+Returns the finalUrl() as a string.
+*/
+QString QQmlDataBlob::finalUrlString() const
+{
+ Q_ASSERT(isCompleteOrError() || (m_manager && m_manager->m_thread->isThisThread()));
+ if (m_finalUrlString.isEmpty())
+ m_finalUrlString = m_finalUrl.toString();
+
+ return m_finalUrlString;
+}
+
+/*!
+Return the errors on this blob.
+
+May only be called from the load thread, or after the blob isCompleteOrError().
+*/
+QList<QQmlError> QQmlDataBlob::errors() const
+{
+ Q_ASSERT(isCompleteOrError() || (m_manager && m_manager->m_thread->isThisThread()));
+ return m_errors;
+}
+
+/*!
+Mark this blob as having \a errors.
+
+All outstanding dependencies will be cancelled. Requests to add new dependencies
+will be ignored. Entry into the Error state is irreversable.
+
+The setError() method may only be called from within a QQmlDataBlob callback.
+*/
+void QQmlDataBlob::setError(const QQmlError &errors)
+{
+ ASSERT_CALLBACK();
+
+ QList<QQmlError> l;
+ l << errors;
+ setError(l);
+}
+
+/*!
+\overload
+*/
+void QQmlDataBlob::setError(const QList<QQmlError> &errors)
+{
+ ASSERT_CALLBACK();
+
+ Q_ASSERT(status() != Error);
+ Q_ASSERT(m_errors.isEmpty());
+
+ m_errors = errors; // Must be set before the m_data fence
+ m_data.setStatus(Error);
+
+ cancelAllWaitingFor();
+
+ if (!m_inCallback)
+ tryDone();
+}
+
+/*!
+Wait for \a blob to become complete or to error. If \a blob is already
+complete or in error, or this blob is already complete, this has no effect.
+
+The setError() method may only be called from within a QQmlDataBlob callback.
+*/
+void QQmlDataBlob::addDependency(QQmlDataBlob *blob)
+{
+ ASSERT_CALLBACK();
+
+ Q_ASSERT(status() != Null);
+
+ if (!blob ||
+ blob->status() == Error || blob->status() == Complete ||
+ status() == Error || status() == Complete || m_isDone ||
+ m_waitingFor.contains(blob))
+ return;
+
+ blob->addref();
+
+ m_data.setStatus(WaitingForDependencies);
+
+ m_waitingFor.append(blob);
+ blob->m_waitingOnMe.append(this);
+}
+
+/*!
+\fn void QQmlDataBlob::dataReceived(const QByteArray &data)
+
+Invoked when data for the blob is received. Implementors should use this callback
+to determine a blob's dependencies. Within this callback you may call setError()
+or addDependency().
+*/
+
+/*!
+Invoked once data has either been received or a network error occurred, and all
+dependencies are complete.
+
+You can set an error in this method, but you cannot add new dependencies. Implementors
+should use this callback to finalize processing of data.
+
+The default implementation does nothing.
+
+XXX Rename processData() or some such to avoid confusion between done() (processing thread)
+and completed() (main thread)
+*/
+void QQmlDataBlob::done()
+{
+}
+
+/*!
+Invoked if there is a network error while fetching this blob.
+
+The default implementation sets an appropriate QQmlError.
+*/
+void QQmlDataBlob::networkError(QNetworkReply::NetworkError networkError)
+{
+ Q_UNUSED(networkError);
+
+ QQmlError error;
+ error.setUrl(m_finalUrl);
+
+ const char *errorString = 0;
+ switch (networkError) {
+ default:
+ errorString = "Network error";
+ break;
+ case QNetworkReply::ConnectionRefusedError:
+ errorString = "Connection refused";
+ break;
+ case QNetworkReply::RemoteHostClosedError:
+ errorString = "Remote host closed the connection";
+ break;
+ case QNetworkReply::HostNotFoundError:
+ errorString = "Host not found";
+ break;
+ case QNetworkReply::TimeoutError:
+ errorString = "Timeout";
+ break;
+ case QNetworkReply::ProxyConnectionRefusedError:
+ case QNetworkReply::ProxyConnectionClosedError:
+ case QNetworkReply::ProxyNotFoundError:
+ case QNetworkReply::ProxyTimeoutError:
+ case QNetworkReply::ProxyAuthenticationRequiredError:
+ case QNetworkReply::UnknownProxyError:
+ errorString = "Proxy error";
+ break;
+ case QNetworkReply::ContentAccessDenied:
+ errorString = "Access denied";
+ break;
+ case QNetworkReply::ContentNotFoundError:
+ errorString = "File not found";
+ break;
+ case QNetworkReply::AuthenticationRequiredError:
+ errorString = "Authentication required";
+ break;
+ };
+
+ error.setDescription(QLatin1String(errorString));
+
+ setError(error);
+}
+
+/*!
+Called if \a blob, which was previously waited for, has an error.
+
+The default implementation does nothing.
+*/
+void QQmlDataBlob::dependencyError(QQmlDataBlob *blob)
+{
+ Q_UNUSED(blob);
+}
+
+/*!
+Called if \a blob, which was previously waited for, has completed.
+
+The default implementation does nothing.
+*/
+void QQmlDataBlob::dependencyComplete(QQmlDataBlob *blob)
+{
+ Q_UNUSED(blob);
+}
+
+/*!
+Called when all blobs waited for have completed. This occurs regardless of
+whether they are in error, or complete state.
+
+The default implementation does nothing.
+*/
+void QQmlDataBlob::allDependenciesDone()
+{
+}
+
+/*!
+Called when the download progress of this blob changes. \a progress goes
+from 0 to 1.
+
+This callback is only invoked if an asynchronous load for this blob is
+made. An asynchronous load is one in which the Asynchronous mode is
+specified explicitly, or one that is implicitly delayed due to a network
+operation.
+
+The default implementation does nothing.
+*/
+void QQmlDataBlob::downloadProgressChanged(qreal progress)
+{
+ Q_UNUSED(progress);
+}
+
+/*!
+Invoked on the main thread sometime after done() was called on the load thread.
+
+You cannot modify the blobs state at all in this callback and cannot depend on the
+order or timeliness of these callbacks. Implementors should use this callback to notify
+dependencies on the main thread that the blob is done and not a lot else.
+
+This callback is only invoked if an asynchronous load for this blob is
+made. An asynchronous load is one in which the Asynchronous mode is
+specified explicitly, or one that is implicitly delayed due to a network
+operation.
+
+The default implementation does nothing.
+*/
+void QQmlDataBlob::completed()
+{
+}
+
+
+void QQmlDataBlob::tryDone()
+{
+ if (status() != Loading && m_waitingFor.isEmpty() && !m_isDone) {
+ m_isDone = true;
+ addref();
+
+#ifdef DATABLOB_DEBUG
+ qWarning("QQmlDataBlob::done() %s", qPrintable(url().toString()));
+#endif
+ done();
+
+ if (status() != Error)
+ m_data.setStatus(Complete);
+
+ notifyAllWaitingOnMe();
+
+ // Locking is not required here, as anyone expecting callbacks must
+ // already be protected against the blob being completed (as set above);
+ if (m_data.isAsync()) {
+#ifdef DATABLOB_DEBUG
+ qWarning("QQmlDataBlob: Dispatching completed");
+#endif
+ m_manager->m_thread->callCompleted(this);
+ }
+
+ release();
+ }
+}
+
+void QQmlDataBlob::cancelAllWaitingFor()
+{
+ while (m_waitingFor.count()) {
+ QQmlDataBlob *blob = m_waitingFor.takeLast();
+
+ Q_ASSERT(blob->m_waitingOnMe.contains(this));
+
+ blob->m_waitingOnMe.removeOne(this);
+
+ blob->release();
+ }
+}
+
+void QQmlDataBlob::notifyAllWaitingOnMe()
+{
+ while (m_waitingOnMe.count()) {
+ QQmlDataBlob *blob = m_waitingOnMe.takeLast();
+
+ Q_ASSERT(blob->m_waitingFor.contains(this));
+
+ blob->notifyComplete(this);
+ }
+}
+
+void QQmlDataBlob::notifyComplete(QQmlDataBlob *blob)
+{
+ Q_ASSERT(m_waitingFor.contains(blob));
+ Q_ASSERT(blob->status() == Error || blob->status() == Complete);
+
+ m_inCallback = true;
+
+ if (blob->status() == Error) {
+ dependencyError(blob);
+ } else if (blob->status() == Complete) {
+ dependencyComplete(blob);
+ }
+
+ m_waitingFor.removeOne(blob);
+ blob->release();
+
+ if (!isError() && m_waitingFor.isEmpty())
+ allDependenciesDone();
+
+ m_inCallback = false;
+
+ tryDone();
+}
+
+#define TD_STATUS_MASK 0x0000FFFF
+#define TD_STATUS_SHIFT 0
+#define TD_PROGRESS_MASK 0x00FF0000
+#define TD_PROGRESS_SHIFT 16
+#define TD_ASYNC_MASK 0x80000000
+
+QQmlDataBlob::ThreadData::ThreadData()
+: _p(0)
+{
+}
+
+QQmlDataBlob::Status QQmlDataBlob::ThreadData::status() const
+{
+ return QQmlDataBlob::Status((_p.load() & TD_STATUS_MASK) >> TD_STATUS_SHIFT);
+}
+
+void QQmlDataBlob::ThreadData::setStatus(QQmlDataBlob::Status status)
+{
+ while (true) {
+ int d = _p.load();
+ int nd = (d & ~TD_STATUS_MASK) | ((status << TD_STATUS_SHIFT) & TD_STATUS_MASK);
+ if (d == nd || _p.testAndSetOrdered(d, nd)) return;
+ }
+}
+
+bool QQmlDataBlob::ThreadData::isAsync() const
+{
+ return _p.load() & TD_ASYNC_MASK;
+}
+
+void QQmlDataBlob::ThreadData::setIsAsync(bool v)
+{
+ while (true) {
+ int d = _p.load();
+ int nd = (d & ~TD_ASYNC_MASK) | (v?TD_ASYNC_MASK:0);
+ if (d == nd || _p.testAndSetOrdered(d, nd)) return;
+ }
+}
+
+quint8 QQmlDataBlob::ThreadData::progress() const
+{
+ return quint8((_p.load() & TD_PROGRESS_MASK) >> TD_PROGRESS_SHIFT);
+}
+
+void QQmlDataBlob::ThreadData::setProgress(quint8 v)
+{
+ while (true) {
+ int d = _p.load();
+ int nd = (d & ~TD_PROGRESS_MASK) | ((v << TD_PROGRESS_SHIFT) & TD_PROGRESS_MASK);
+ if (d == nd || _p.testAndSetOrdered(d, nd)) return;
+ }
+}
+
+QQmlDataLoaderThread::QQmlDataLoaderThread(QQmlDataLoader *loader)
+: m_loader(loader), m_networkAccessManager(0), m_networkReplyProxy(0)
+{
+}
+
+QNetworkAccessManager *QQmlDataLoaderThread::networkAccessManager() const
+{
+ Q_ASSERT(isThisThread());
+ if (!m_networkAccessManager) {
+ m_networkAccessManager = QQmlEnginePrivate::get(m_loader->engine())->createNetworkAccessManager(0);
+ m_networkReplyProxy = new QQmlDataLoaderNetworkReplyProxy(m_loader);
+ }
+
+ return m_networkAccessManager;
+}
+
+QQmlDataLoaderNetworkReplyProxy *QQmlDataLoaderThread::networkReplyProxy() const
+{
+ Q_ASSERT(isThisThread());
+ Q_ASSERT(m_networkReplyProxy); // Must call networkAccessManager() first
+ return m_networkReplyProxy;
+}
+
+void QQmlDataLoaderThread::load(QQmlDataBlob *b)
+{
+ b->addref();
+ callMethodInThread(&This::loadThread, b);
+}
+
+void QQmlDataLoaderThread::loadAsync(QQmlDataBlob *b)
+{
+ b->addref();
+ postMethodToThread(&This::loadThread, b);
+}
+
+void QQmlDataLoaderThread::loadWithStaticData(QQmlDataBlob *b, const QByteArray &d)
+{
+ b->addref();
+ callMethodInThread(&This::loadWithStaticDataThread, b, d);
+}
+
+void QQmlDataLoaderThread::loadWithStaticDataAsync(QQmlDataBlob *b, const QByteArray &d)
+{
+ b->addref();
+ postMethodToThread(&This::loadWithStaticDataThread, b, d);
+}
+
+void QQmlDataLoaderThread::callCompleted(QQmlDataBlob *b)
+{
+ b->addref();
+ postMethodToMain(&This::callCompletedMain, b);
+}
+
+void QQmlDataLoaderThread::callDownloadProgressChanged(QQmlDataBlob *b, qreal p)
+{
+ b->addref();
+ postMethodToMain(&This::callDownloadProgressChangedMain, b, p);
+}
+
+void QQmlDataLoaderThread::initializeEngine(QQmlExtensionInterface *iface,
+ const char *uri)
+{
+ callMethodInMain(&This::initializeEngineMain, iface, uri);
+}
+
+void QQmlDataLoaderThread::shutdownThread()
+{
+ delete m_networkAccessManager;
+ m_networkAccessManager = 0;
+ delete m_networkReplyProxy;
+ m_networkReplyProxy = 0;
+}
+
+void QQmlDataLoaderThread::loadThread(QQmlDataBlob *b)
+{
+ m_loader->loadThread(b);
+ b->release();
+}
+
+void QQmlDataLoaderThread::loadWithStaticDataThread(QQmlDataBlob *b, const QByteArray &d)
+{
+ m_loader->loadWithStaticDataThread(b, d);
+ b->release();
+}
+
+void QQmlDataLoaderThread::callCompletedMain(QQmlDataBlob *b)
+{
+#ifdef DATABLOB_DEBUG
+ qWarning("QQmlDataLoaderThread: %s completed() callback", qPrintable(b->url().toString()));
+#endif
+ b->completed();
+ b->release();
+}
+
+void QQmlDataLoaderThread::callDownloadProgressChangedMain(QQmlDataBlob *b, qreal p)
+{
+#ifdef DATABLOB_DEBUG
+ qWarning("QQmlDataLoaderThread: %s downloadProgressChanged(%f) callback",
+ qPrintable(b->url().toString()), p);
+#endif
+ b->downloadProgressChanged(p);
+ b->release();
+}
+
+void QQmlDataLoaderThread::initializeEngineMain(QQmlExtensionInterface *iface,
+ const char *uri)
+{
+ Q_ASSERT(m_loader->engine()->thread() == QThread::currentThread());
+ iface->initializeEngine(m_loader->engine(), uri);
+}
+
+/*!
+\class QQmlDataLoader
+\brief The QQmlDataLoader class abstracts loading files and their dependencies over the network.
+\internal
+
+The QQmlDataLoader class is provided for the exclusive use of the QQmlTypeLoader class.
+
+Clients create QQmlDataBlob instances and submit them to the QQmlDataLoader class
+through the QQmlDataLoader::load() or QQmlDataLoader::loadWithStaticData() methods.
+The loader then fetches the data over the network or from the local file system in an efficient way.
+QQmlDataBlob is an abstract class, so should always be specialized.
+
+Once data is received, the QQmlDataBlob::dataReceived() method is invoked on the blob. The
+derived class should use this callback to process the received data. Processing of the data can
+result in an error being set (QQmlDataBlob::setError()), or one or more dependencies being
+created (QQmlDataBlob::addDependency()). Dependencies are other QQmlDataBlob's that
+are required before processing can fully complete.
+
+To complete processing, the QQmlDataBlob::done() callback is invoked. done() is called when
+one of these three preconditions are met.
+
+\list 1
+\o The QQmlDataBlob has no dependencies.
+\o The QQmlDataBlob has an error set.
+\o All the QQmlDataBlob's dependencies are themselves "done()".
+\endlist
+
+Thus QQmlDataBlob::done() will always eventually be called, even if the blob has an error set.
+*/
+
+/*!
+Create a new QQmlDataLoader for \a engine.
+*/
+QQmlDataLoader::QQmlDataLoader(QQmlEngine *engine)
+: m_engine(engine), m_thread(new QQmlDataLoaderThread(this))
+{
+}
+
+/*! \internal */
+QQmlDataLoader::~QQmlDataLoader()
+{
+ for (NetworkReplies::Iterator iter = m_networkReplies.begin(); iter != m_networkReplies.end(); ++iter)
+ (*iter)->release();
+
+ m_thread->shutdown();
+ delete m_thread;
+}
+
+void QQmlDataLoader::lock()
+{
+ m_thread->lock();
+}
+
+void QQmlDataLoader::unlock()
+{
+ m_thread->unlock();
+}
+
+/*!
+Load the provided \a blob from the network or filesystem.
+
+The loader must be locked.
+*/
+void QQmlDataLoader::load(QQmlDataBlob *blob, Mode mode)
+{
+#ifdef DATABLOB_DEBUG
+ qWarning("QQmlDataLoader::load(%s): %s thread", qPrintable(blob->m_url.toString()),
+ m_thread->isThisThread()?"Compile":"Engine");
+#endif
+
+ Q_ASSERT(blob->status() == QQmlDataBlob::Null);
+ Q_ASSERT(blob->m_manager == 0);
+
+ blob->m_data.setStatus(QQmlDataBlob::Loading);
+ blob->m_manager = this;
+
+ if (m_thread->isThisThread()) {
+ unlock();
+ loadThread(blob);
+ lock();
+ } else if (mode == PreferSynchronous) {
+ unlock();
+ m_thread->load(blob);
+ lock();
+ if (!blob->isCompleteOrError())
+ blob->m_data.setIsAsync(true);
+ } else {
+ Q_ASSERT(mode == Asynchronous);
+ blob->m_data.setIsAsync(true);
+ unlock();
+ m_thread->loadAsync(blob);
+ lock();
+ }
+}
+
+/*!
+Load the provided \a blob with \a data. The blob's URL is not used by the data loader in this case.
+
+The loader must be locked.
+*/
+void QQmlDataLoader::loadWithStaticData(QQmlDataBlob *blob, const QByteArray &data, Mode mode)
+{
+#ifdef DATABLOB_DEBUG
+ qWarning("QQmlDataLoader::loadWithStaticData(%s, data): %s thread", qPrintable(blob->m_url.toString()),
+ m_thread->isThisThread()?"Compile":"Engine");
+#endif
+
+ Q_ASSERT(blob->status() == QQmlDataBlob::Null);
+ Q_ASSERT(blob->m_manager == 0);
+
+ blob->m_data.setStatus(QQmlDataBlob::Loading);
+ blob->m_manager = this;
+
+ if (m_thread->isThisThread()) {
+ unlock();
+ loadWithStaticDataThread(blob, data);
+ lock();
+ } else if (mode == PreferSynchronous) {
+ unlock();
+ m_thread->loadWithStaticData(blob, data);
+ lock();
+ if (!blob->isCompleteOrError())
+ blob->m_data.setIsAsync(true);
+ } else {
+ Q_ASSERT(mode == Asynchronous);
+ blob->m_data.setIsAsync(true);
+ unlock();
+ m_thread->loadWithStaticDataAsync(blob, data);
+ lock();
+ }
+}
+
+void QQmlDataLoader::loadWithStaticDataThread(QQmlDataBlob *blob, const QByteArray &data)
+{
+ ASSERT_LOADTHREAD();
+
+ setData(blob, data);
+}
+
+void QQmlDataLoader::loadThread(QQmlDataBlob *blob)
+{
+ ASSERT_LOADTHREAD();
+
+ if (blob->m_url.isEmpty()) {
+ QQmlError error;
+ error.setDescription(QLatin1String("Invalid null URL"));
+ blob->setError(error);
+ return;
+ }
+
+ QString lf = QQmlEnginePrivate::urlToLocalFileOrQrc(blob->m_url);
+
+ if (!lf.isEmpty()) {
+ if (!QQml_isFileCaseCorrect(lf)) {
+ QQmlError error;
+ error.setUrl(blob->m_url);
+ error.setDescription(QLatin1String("File name case mismatch"));
+ blob->setError(error);
+ return;
+ }
+ QFile file(lf);
+ if (file.open(QFile::ReadOnly)) {
+ QByteArray data = file.readAll();
+
+ blob->m_data.setProgress(0xFF);
+ if (blob->m_data.isAsync())
+ m_thread->callDownloadProgressChanged(blob, 1.);
+
+ setData(blob, data);
+ } else {
+ blob->networkError(QNetworkReply::ContentNotFoundError);
+ }
+
+ } else {
+
+ QNetworkReply *reply = m_thread->networkAccessManager()->get(QNetworkRequest(blob->m_url));
+ QObject *nrp = m_thread->networkReplyProxy();
+ QObject::connect(reply, SIGNAL(downloadProgress(qint64,qint64)),
+ nrp, SLOT(downloadProgress(qint64,qint64)));
+ QObject::connect(reply, SIGNAL(finished()),
+ nrp, SLOT(finished()));
+ m_networkReplies.insert(reply, blob);
+
+ blob->addref();
+ }
+}
+
+#define DATALOADER_MAXIMUM_REDIRECT_RECURSION 16
+
+void QQmlDataLoader::networkReplyFinished(QNetworkReply *reply)
+{
+ Q_ASSERT(m_thread->isThisThread());
+
+ reply->deleteLater();
+
+ QQmlDataBlob *blob = m_networkReplies.take(reply);
+
+ Q_ASSERT(blob);
+
+ blob->m_redirectCount++;
+
+ if (blob->m_redirectCount < DATALOADER_MAXIMUM_REDIRECT_RECURSION) {
+ QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
+ if (redirect.isValid()) {
+ QUrl url = reply->url().resolved(redirect.toUrl());
+ blob->m_finalUrl = url;
+
+ QNetworkReply *reply = m_thread->networkAccessManager()->get(QNetworkRequest(url));
+ QObject *nrp = m_thread->networkReplyProxy();
+ QObject::connect(reply, SIGNAL(finished()), nrp, SLOT(finished()));
+ m_networkReplies.insert(reply, blob);
+ return;
+ }
+ }
+
+ if (reply->error()) {
+ blob->networkError(reply->error());
+ } else {
+ QByteArray data = reply->readAll();
+ setData(blob, data);
+ }
+
+ blob->release();
+}
+
+void QQmlDataLoader::networkReplyProgress(QNetworkReply *reply,
+ qint64 bytesReceived, qint64 bytesTotal)
+{
+ Q_ASSERT(m_thread->isThisThread());
+
+ QQmlDataBlob *blob = m_networkReplies.value(reply);
+
+ Q_ASSERT(blob);
+
+ if (bytesTotal != 0) {
+ quint8 progress = 0xFF * (qreal(bytesReceived) / qreal(bytesTotal));
+ blob->m_data.setProgress(progress);
+ if (blob->m_data.isAsync())
+ m_thread->callDownloadProgressChanged(blob, blob->m_data.progress());
+ }
+}
+
+/*!
+Return the QQmlEngine associated with this loader
+*/
+QQmlEngine *QQmlDataLoader::engine() const
+{
+ return m_engine;
+}
+
+/*!
+Call the initializeEngine() method on \a iface. Used by QQmlImportDatabase to ensure it
+gets called in the correct thread.
+*/
+void QQmlDataLoader::initializeEngine(QQmlExtensionInterface *iface,
+ const char *uri)
+{
+ Q_ASSERT(m_thread->isThisThread() || engine()->thread() == QThread::currentThread());
+
+ if (m_thread->isThisThread()) {
+ m_thread->initializeEngine(iface, uri);
+ } else {
+ Q_ASSERT(engine()->thread() == QThread::currentThread());
+ iface->initializeEngine(engine(), uri);
+ }
+}
+
+
+void QQmlDataLoader::setData(QQmlDataBlob *blob, const QByteArray &data)
+{
+ blob->m_inCallback = true;
+
+ blob->dataReceived(data);
+
+ if (!blob->isError() && !blob->isWaiting())
+ blob->allDependenciesDone();
+
+ if (blob->status() != QQmlDataBlob::Error)
+ blob->m_data.setStatus(QQmlDataBlob::WaitingForDependencies);
+
+ blob->m_inCallback = false;
+
+ blob->tryDone();
+}
+
+/*!
+Constructs a new type loader that uses the given \a engine.
+*/
+QQmlTypeLoader::QQmlTypeLoader(QQmlEngine *engine)
+: QQmlDataLoader(engine)
+{
+}
+
+/*!
+Destroys the type loader, first clearing the cache of any information about
+loaded files.
+*/
+QQmlTypeLoader::~QQmlTypeLoader()
+{
+ clearCache();
+}
+
+/*!
+\enum QQmlTypeLoader::Option
+
+This enum defines the options that control the way type data is handled.
+
+\value None The default value, indicating that no other options
+ are enabled.
+\value PreserveParser The parser used to handle the type data is preserved
+ after the data has been parsed.
+*/
+
+/*!
+Returns a QQmlTypeData for the specified \a url. The QQmlTypeData may be cached.
+*/
+QQmlTypeData *QQmlTypeLoader::get(const QUrl &url)
+{
+ Q_ASSERT(!url.isRelative() &&
+ (QQmlEnginePrivate::urlToLocalFileOrQrc(url).isEmpty() ||
+ !QDir::isRelativePath(QQmlEnginePrivate::urlToLocalFileOrQrc(url))));
+
+ lock();
+
+ QQmlTypeData *typeData = m_typeCache.value(url);
+
+ if (!typeData) {
+ typeData = new QQmlTypeData(url, None, this);
+ m_typeCache.insert(url, typeData);
+ QQmlDataLoader::load(typeData);
+ }
+
+ typeData->addref();
+
+ unlock();
+
+ return typeData;
+}
+
+/*!
+Returns a QQmlTypeData for the given \a data with the provided base \a url. The
+QQmlTypeData will not be cached.
+
+The specified \a options control how the loader handles type data.
+*/
+QQmlTypeData *QQmlTypeLoader::get(const QByteArray &data, const QUrl &url, Options options)
+{
+ lock();
+
+ QQmlTypeData *typeData = new QQmlTypeData(url, options, this);
+ QQmlDataLoader::loadWithStaticData(typeData, data);
+
+ unlock();
+
+ return typeData;
+}
+
+/*!
+Return a QQmlScriptBlob for \a url. The QQmlScriptData may be cached.
+*/
+QQmlScriptBlob *QQmlTypeLoader::getScript(const QUrl &url)
+{
+ Q_ASSERT(!url.isRelative() &&
+ (QQmlEnginePrivate::urlToLocalFileOrQrc(url).isEmpty() ||
+ !QDir::isRelativePath(QQmlEnginePrivate::urlToLocalFileOrQrc(url))));
+
+ lock();
+
+ QQmlScriptBlob *scriptBlob = m_scriptCache.value(url);
+
+ if (!scriptBlob) {
+ scriptBlob = new QQmlScriptBlob(url, this);
+ m_scriptCache.insert(url, scriptBlob);
+ QQmlDataLoader::load(scriptBlob);
+ }
+
+ scriptBlob->addref();
+
+ unlock();
+
+ return scriptBlob;
+}
+
+/*!
+Returns a QQmlQmldirData for \a url. The QQmlQmldirData may be cached.
+*/
+QQmlQmldirData *QQmlTypeLoader::getQmldir(const QUrl &url)
+{
+ Q_ASSERT(!url.isRelative() &&
+ (QQmlEnginePrivate::urlToLocalFileOrQrc(url).isEmpty() ||
+ !QDir::isRelativePath(QQmlEnginePrivate::urlToLocalFileOrQrc(url))));
+
+ lock();
+
+ QQmlQmldirData *qmldirData = m_qmldirCache.value(url);
+
+ if (!qmldirData) {
+ qmldirData = new QQmlQmldirData(url);
+ m_qmldirCache.insert(url, qmldirData);
+ QQmlDataLoader::load(qmldirData);
+ }
+
+ qmldirData->addref();
+
+ unlock();
+
+ return qmldirData;
+}
+
+/*!
+Returns the absolute filename of path via a directory cache for files named
+"qmldir", "*.qml", "*.js", and plugins.
+Returns a empty string if the path does not exist.
+*/
+QString QQmlTypeLoader::absoluteFilePath(const QString &path)
+{
+ if (path.isEmpty())
+ return QString();
+ if (path.at(0) == QLatin1Char(':')) {
+ // qrc resource
+ QFileInfo fileInfo(path);
+ return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString();
+ }
+#if defined(Q_OS_WIN32) || defined(Q_OS_WINCE) || defined(Q_OS_DARWIN)
+ QString lowPath = path.toLower();
+ int lastSlash = lowPath.lastIndexOf(QLatin1Char('/'));
+ QString dirPath = lowPath.left(lastSlash);
+#else
+ int lastSlash = path.lastIndexOf(QLatin1Char('/'));
+ QStringRef dirPath(&path, 0, lastSlash);
+#endif
+
+ StringSet **fileSet = m_importDirCache.value(QHashedStringRef(dirPath.constData(), dirPath.length()));
+ if (!fileSet) {
+#if defined(Q_OS_WIN32) || defined(Q_OS_WINCE) || defined(Q_OS_DARWIN)
+ QHashedString dirPathString(dirPath);
+#else
+ QHashedString dirPathString(dirPath.toString());
+#endif
+ StringSet *files = qmlFilesInDirectory(dirPathString);
+ m_importDirCache.insert(dirPathString, files);
+ fileSet = m_importDirCache.value(dirPathString);
+ }
+ if (!(*fileSet))
+ return QString();
+
+#if defined(Q_OS_WIN32) || defined(Q_OS_WINCE) || defined(Q_OS_DARWIN)
+ QString absoluteFilePath = (*fileSet)->contains(QHashedStringRef(lowPath.constData()+lastSlash+1, lowPath.length()-lastSlash-1)) ? path : QString();
+#else
+ QString absoluteFilePath = (*fileSet)->contains(QHashedStringRef(path.constData()+lastSlash+1, path.length()-lastSlash-1)) ? path : QString();
+#endif
+ if (absoluteFilePath.length() > 2 && absoluteFilePath.at(0) != QLatin1Char('/') && absoluteFilePath.at(1) != QLatin1Char(':'))
+ absoluteFilePath = QFileInfo(absoluteFilePath).absoluteFilePath();
+
+ return absoluteFilePath;
+}
+
+/*!
+Returns true if the path is a directory via a directory cache. Cache is
+shared with absoluteFilePath().
+*/
+bool QQmlTypeLoader::directoryExists(const QString &path)
+{
+ if (path.isEmpty())
+ return false;
+ if (path.at(0) == QLatin1Char(':')) {
+ // qrc resource
+ QFileInfo fileInfo(path);
+ return fileInfo.exists() && fileInfo.isDir();
+ }
+
+ int length = path.length();
+ if (path.endsWith(QLatin1Char('/')))
+ --length;
+#if defined(Q_OS_WIN32) || defined(Q_OS_WINCE) || defined(Q_OS_DARWIN)
+ QString dirPath = path.left(length).toLower();
+#else
+ QStringRef dirPath(&path, 0, length);
+#endif
+
+ StringSet **fileSet = m_importDirCache.value(QHashedStringRef(dirPath.constData(), dirPath.length()));
+ if (!fileSet) {
+#if defined(Q_OS_WIN32) || defined(Q_OS_WINCE) || defined(Q_OS_DARWIN)
+ QHashedString dirPathString(dirPath);
+#else
+ QHashedString dirPathString(dirPath.toString());
+#endif
+ StringSet *files = qmlFilesInDirectory(dirPathString);
+ m_importDirCache.insert(dirPathString, files);
+ fileSet = m_importDirCache.value(dirPathString);
+ }
+
+ return (*fileSet);
+}
+
+
+/*!
+Return a QQmlDirParser for absoluteFilePath. The QQmlDirParser may be cached.
+*/
+const QQmlDirParser *QQmlTypeLoader::qmlDirParser(const QString &absoluteFilePath)
+{
+ QQmlDirParser *qmldirParser;
+ QQmlDirParser **val = m_importQmlDirCache.value(absoluteFilePath);
+ if (!val) {
+ qmldirParser = new QQmlDirParser;
+ qmldirParser->setFileSource(absoluteFilePath);
+ qmldirParser->setUrl(QUrl::fromLocalFile(absoluteFilePath));
+ qmldirParser->parse();
+ m_importQmlDirCache.insert(absoluteFilePath, qmldirParser);
+ } else {
+ qmldirParser = *val;
+ }
+
+ return qmldirParser;
+}
+
+
+/*!
+Clears cached information about loaded files, including any type data, scripts
+and qmldir information.
+*/
+void QQmlTypeLoader::clearCache()
+{
+ for (TypeCache::Iterator iter = m_typeCache.begin(); iter != m_typeCache.end(); ++iter)
+ (*iter)->release();
+ for (ScriptCache::Iterator iter = m_scriptCache.begin(); iter != m_scriptCache.end(); ++iter)
+ (*iter)->release();
+ for (QmldirCache::Iterator iter = m_qmldirCache.begin(); iter != m_qmldirCache.end(); ++iter)
+ (*iter)->release();
+ qDeleteAll(m_importDirCache);
+ qDeleteAll(m_importQmlDirCache);
+
+ m_typeCache.clear();
+ m_scriptCache.clear();
+ m_qmldirCache.clear();
+ m_importDirCache.clear();
+ m_importQmlDirCache.clear();
+}
+
+
+QQmlTypeData::QQmlTypeData(const QUrl &url, QQmlTypeLoader::Options options,
+ QQmlTypeLoader *manager)
+: QQmlDataBlob(url, QmlFile), m_options(options), m_imports(manager), m_typesResolved(false),
+ m_compiledData(0), m_typeLoader(manager)
+{
+}
+
+QQmlTypeData::~QQmlTypeData()
+{
+ for (int ii = 0; ii < m_scripts.count(); ++ii)
+ m_scripts.at(ii).script->release();
+ for (int ii = 0; ii < m_qmldirs.count(); ++ii)
+ m_qmldirs.at(ii)->release();
+ for (int ii = 0; ii < m_types.count(); ++ii)
+ if (m_types.at(ii).typeData) m_types.at(ii).typeData->release();
+ if (m_compiledData)
+ m_compiledData->release();
+}
+
+QQmlTypeLoader *QQmlTypeData::typeLoader() const
+{
+ return m_typeLoader;
+}
+
+const QQmlImports &QQmlTypeData::imports() const
+{
+ return m_imports;
+}
+
+const QQmlScript::Parser &QQmlTypeData::parser() const
+{
+ return scriptParser;
+}
+
+const QList<QQmlTypeData::TypeReference> &QQmlTypeData::resolvedTypes() const
+{
+ return m_types;
+}
+
+const QList<QQmlTypeData::ScriptReference> &QQmlTypeData::resolvedScripts() const
+{
+ return m_scripts;
+}
+
+const QSet<QString> &QQmlTypeData::namespaces() const
+{
+ return m_namespaces;
+}
+
+QQmlCompiledData *QQmlTypeData::compiledData() const
+{
+ if (m_compiledData)
+ m_compiledData->addref();
+
+ return m_compiledData;
+}
+
+void QQmlTypeData::registerCallback(TypeDataCallback *callback)
+{
+ Q_ASSERT(!m_callbacks.contains(callback));
+ m_callbacks.append(callback);
+}
+
+void QQmlTypeData::unregisterCallback(TypeDataCallback *callback)
+{
+ Q_ASSERT(m_callbacks.contains(callback));
+ m_callbacks.removeOne(callback);
+ Q_ASSERT(!m_callbacks.contains(callback));
+}
+
+void QQmlTypeData::done()
+{
+ // Check all script dependencies for errors
+ for (int ii = 0; !isError() && ii < m_scripts.count(); ++ii) {
+ const ScriptReference &script = m_scripts.at(ii);
+ Q_ASSERT(script.script->isCompleteOrError());
+ if (script.script->isError()) {
+ QList<QQmlError> errors = script.script->errors();
+ QQmlError error;
+ error.setUrl(finalUrl());
+ error.setLine(script.location.line);
+ error.setColumn(script.location.column);
+ error.setDescription(QQmlTypeLoader::tr("Script %1 unavailable").arg(script.script->url().toString()));
+ errors.prepend(error);
+ setError(errors);
+ }
+ }
+
+ // Check all type dependencies for errors
+ for (int ii = 0; !isError() && ii < m_types.count(); ++ii) {
+ const TypeReference &type = m_types.at(ii);
+ Q_ASSERT(!type.typeData || type.typeData->isCompleteOrError());
+ if (type.typeData && type.typeData->isError()) {
+ QString typeName = scriptParser.referencedTypes().at(ii)->name;
+
+ QList<QQmlError> errors = type.typeData->errors();
+ QQmlError error;
+ error.setUrl(finalUrl());
+ error.setLine(type.location.line);
+ error.setColumn(type.location.column);
+ error.setDescription(QQmlTypeLoader::tr("Type %1 unavailable").arg(typeName));
+ errors.prepend(error);
+ setError(errors);
+ }
+ }
+
+ // Compile component
+ if (!isError())
+ compile();
+
+ if (!(m_options & QQmlTypeLoader::PreserveParser))
+ scriptParser.clear();
+}
+
+void QQmlTypeData::completed()
+{
+ // Notify callbacks
+ while (!m_callbacks.isEmpty()) {
+ TypeDataCallback *callback = m_callbacks.takeFirst();
+ callback->typeDataReady(this);
+ }
+}
+
+void QQmlTypeData::dataReceived(const QByteArray &data)
+{
+ if (!scriptParser.parse(data, finalUrl(), finalUrlString())) {
+ setError(scriptParser.errors());
+ return;
+ }
+
+ m_imports.setBaseUrl(finalUrl(), finalUrlString());
+
+ foreach (const QQmlScript::Import &import, scriptParser.imports()) {
+ if (import.type == QQmlScript::Import::File && import.qualifier.isEmpty()) {
+ QUrl importUrl = finalUrl().resolved(QUrl(import.uri + QLatin1String("/qmldir")));
+ if (QQmlEnginePrivate::urlToLocalFileOrQrc(importUrl).isEmpty()) {
+ QQmlQmldirData *data = typeLoader()->getQmldir(importUrl);
+ addDependency(data);
+ m_qmldirs << data;
+ }
+ } else if (import.type == QQmlScript::Import::Script) {
+ QUrl scriptUrl = finalUrl().resolved(QUrl(import.uri));
+ QQmlScriptBlob *blob = typeLoader()->getScript(scriptUrl);
+ addDependency(blob);
+
+ ScriptReference ref;
+ ref.location = import.location.start;
+ ref.qualifier = import.qualifier;
+ ref.script = blob;
+ m_scripts << ref;
+
+ }
+ }
+
+ if (!finalUrl().scheme().isEmpty()) {
+ QUrl importUrl = finalUrl().resolved(QUrl(QLatin1String("qmldir")));
+ if (QQmlEnginePrivate::urlToLocalFileOrQrc(importUrl).isEmpty()) {
+ QQmlQmldirData *data = typeLoader()->getQmldir(importUrl);
+ addDependency(data);
+ m_qmldirs << data;
+ }
+ }
+}
+
+void QQmlTypeData::allDependenciesDone()
+{
+ if (!m_typesResolved) {
+ resolveTypes();
+ m_typesResolved = true;
+ }
+}
+
+void QQmlTypeData::downloadProgressChanged(qreal p)
+{
+ for (int ii = 0; ii < m_callbacks.count(); ++ii) {
+ TypeDataCallback *callback = m_callbacks.at(ii);
+ callback->typeDataProgress(this, p);
+ }
+}
+
+void QQmlTypeData::compile()
+{
+ Q_ASSERT(m_compiledData == 0);
+ QQmlProfilerService::startRange(QQmlProfilerService::Compiling);
+
+ m_compiledData = new QQmlCompiledData(typeLoader()->engine());
+ m_compiledData->url = finalUrl();
+ m_compiledData->name = finalUrlString();
+ QQmlProfilerService::rangeLocation(QQmlProfilerService::Compiling, QUrl(m_compiledData->name),1,1);
+ QQmlProfilerService::rangeData(QQmlProfilerService::Compiling, m_compiledData->name);
+
+ QQmlCompiler compiler(&scriptParser._pool);
+ if (!compiler.compile(typeLoader()->engine(), this, m_compiledData)) {
+ setError(compiler.errors());
+ m_compiledData->release();
+ m_compiledData = 0;
+ }
+ QQmlProfilerService::endRange(QQmlProfilerService::Compiling);
+}
+
+void QQmlTypeData::resolveTypes()
+{
+ QQmlEnginePrivate *ep = QQmlEnginePrivate::get(m_typeLoader->engine());
+ QQmlImportDatabase *importDatabase = &ep->importDatabase;
+
+ // For local urls, add an implicit import "." as first (most overridden) lookup.
+ // This will also trigger the loading of the qmldir and the import of any native
+ // types from available plugins.
+ QList<QQmlError> errors;
+ if (QQmlQmldirData *qmldir = qmldirForUrl(finalUrl().resolved(QUrl(QLatin1String("./qmldir"))))) {
+ m_imports.addImport(importDatabase, QLatin1String("."),
+ QString(), -1, -1, QQmlScript::Import::File,
+ qmldir->dirComponents(), &errors);
+ } else {
+ m_imports.addImport(importDatabase, QLatin1String("."),
+ QString(), -1, -1, QQmlScript::Import::File,
+ QQmlDirComponents(), &errors);
+ }
+
+ // remove any errors which are due to the implicit import which aren't real errors.
+ // for example, if the implicitly included qmldir file doesn't exist, that is not an error.
+ QList<QQmlError> realErrors;
+ for (int i = 0; i < errors.size(); ++i) {
+ if (errors.at(i).description() != QQmlImportDatabase::tr("import \".\" has no qmldir and no namespace")
+ && errors.at(i).description() != QQmlImportDatabase::tr("\".\": no such directory")) {
+ realErrors.prepend(errors.at(i)); // this is a real error.
+ }
+ }
+
+ // report any real errors which occurred during plugin loading or qmldir parsing.
+ if (!realErrors.isEmpty()) {
+ setError(realErrors);
+ return;
+ }
+
+ foreach (const QQmlScript::Import &import, scriptParser.imports()) {
+ QQmlDirComponents qmldircomponentsnetwork;
+ if (import.type == QQmlScript::Import::Script)
+ continue;
+
+ if (import.type == QQmlScript::Import::File && import.qualifier.isEmpty()) {
+ QUrl qmldirUrl = finalUrl().resolved(QUrl(import.uri + QLatin1String("/qmldir")));
+ if (QQmlQmldirData *qmldir = qmldirForUrl(qmldirUrl))
+ qmldircomponentsnetwork = qmldir->dirComponents();
+ }
+
+ int vmaj = -1;
+ int vmin = -1;
+ import.extractVersion(&vmaj, &vmin);
+
+ QList<QQmlError> errors;
+ if (!m_imports.addImport(importDatabase, import.uri, import.qualifier,
+ vmaj, vmin, import.type, qmldircomponentsnetwork, &errors)) {
+ QQmlError error;
+ if (errors.size()) {
+ error = errors.takeFirst();
+ } else {
+ // this should not be possible!
+ // Description should come from error provided by addImport() function.
+ error.setDescription(QQmlTypeLoader::tr("Unreported error adding script import to import database"));
+ }
+ error.setUrl(m_imports.baseUrl());
+ error.setLine(import.location.start.line);
+ error.setColumn(import.location.start.column);
+ errors.prepend(error); // put it back on the list after filling out information.
+
+ setError(errors);
+ return;
+ }
+ }
+
+ // Add any imported scripts to our resolved set
+ foreach (const QQmlImports::ScriptReference &script, m_imports.resolvedScripts())
+ {
+ QQmlScriptBlob *blob = typeLoader()->getScript(script.location);
+ addDependency(blob);
+
+ ScriptReference ref;
+ //ref.location = ...
+ ref.qualifier = script.nameSpace;
+ if (!script.qualifier.isEmpty())
+ {
+ ref.qualifier.prepend(script.qualifier + QLatin1Char('.'));
+
+ // Add a reference to the enclosing namespace
+ m_namespaces.insert(script.qualifier);
+ }
+
+ ref.script = blob;
+ m_scripts << ref;
+ }
+
+ foreach (QQmlScript::TypeReference *parserRef, scriptParser.referencedTypes()) {
+ TypeReference ref;
+
+ QString url;
+ int majorVersion;
+ int minorVersion;
+ QQmlImportedNamespace *typeNamespace = 0;
+ QList<QQmlError> errors;
+
+ if (!m_imports.resolveType(parserRef->name, &ref.type, &url, &majorVersion, &minorVersion,
+ &typeNamespace, &errors) || typeNamespace) {
+ // Known to not be a type:
+ // - known to be a namespace (Namespace {})
+ // - type with unknown namespace (UnknownNamespace.SomeType {})
+ QQmlError error;
+ QString userTypeName = parserRef->name;
+ userTypeName.replace(QLatin1Char('/'),QLatin1Char('.'));
+ if (typeNamespace) {
+ error.setDescription(QQmlTypeLoader::tr("Namespace %1 cannot be used as a type").arg(userTypeName));
+ } else {
+ if (errors.size()) {
+ error = errors.takeFirst();
+ } else {
+ // this should not be possible!
+ // Description should come from error provided by addImport() function.
+ error.setDescription(QQmlTypeLoader::tr("Unreported error adding script import to import database"));
+ }
+ error.setUrl(m_imports.baseUrl());
+ error.setDescription(QQmlTypeLoader::tr("%1 %2").arg(userTypeName).arg(error.description()));
+ }
+
+ if (!parserRef->refObjects.isEmpty()) {
+ QQmlScript::Object *obj = parserRef->refObjects.first();
+ error.setLine(obj->location.start.line);
+ error.setColumn(obj->location.start.column);
+ }
+
+ errors.prepend(error);
+ setError(errors);
+ return;
+ }
+
+ if (ref.type) {
+ ref.majorVersion = majorVersion;
+ ref.minorVersion = minorVersion;
+ } else {
+ ref.typeData = typeLoader()->get(QUrl(url));
+ addDependency(ref.typeData);
+ }
+
+ if (parserRef->refObjects.count())
+ ref.location = parserRef->refObjects.first()->location.start;
+
+ m_types << ref;
+ }
+}
+
+QQmlQmldirData *QQmlTypeData::qmldirForUrl(const QUrl &url)
+{
+ for (int ii = 0; ii < m_qmldirs.count(); ++ii) {
+ if (m_qmldirs.at(ii)->url() == url)
+ return m_qmldirs.at(ii);
+ }
+ return 0;
+}
+
+QQmlScriptData::QQmlScriptData()
+: importCache(0), pragmas(QQmlScript::Object::ScriptBlock::None), m_loaded(false)
+{
+}
+
+QQmlScriptData::~QQmlScriptData()
+{
+}
+
+void QQmlScriptData::clear()
+{
+ if (importCache) {
+ importCache->release();
+ importCache = 0;
+ }
+
+ for (int ii = 0; ii < scripts.count(); ++ii)
+ scripts.at(ii)->release();
+ scripts.clear();
+
+ qPersistentDispose(m_program);
+ qPersistentDispose(m_value);
+
+ // An addref() was made when the QQmlCleanup was added to the engine.
+ release();
+}
+
+QQmlScriptBlob::QQmlScriptBlob(const QUrl &url, QQmlTypeLoader *loader)
+: QQmlDataBlob(url, JavaScriptFile), m_pragmas(QQmlScript::Object::ScriptBlock::None),
+ m_imports(loader), m_scriptData(0), m_typeLoader(loader)
+{
+}
+
+QQmlScriptBlob::~QQmlScriptBlob()
+{
+ if (m_scriptData) {
+ m_scriptData->release();
+ m_scriptData = 0;
+ }
+}
+
+QQmlScript::Object::ScriptBlock::Pragmas QQmlScriptBlob::pragmas() const
+{
+ return m_pragmas;
+}
+
+QQmlTypeLoader *QQmlScriptBlob::typeLoader() const
+{
+ return m_typeLoader;
+}
+
+const QQmlImports &QQmlScriptBlob::imports() const
+{
+ return m_imports;
+}
+
+QQmlScriptData *QQmlScriptBlob::scriptData() const
+{
+ return m_scriptData;
+}
+
+void QQmlScriptBlob::dataReceived(const QByteArray &data)
+{
+ QQmlEnginePrivate *ep = QQmlEnginePrivate::get(m_typeLoader->engine());
+ QQmlImportDatabase *importDatabase = &ep->importDatabase;
+
+ m_source = QString::fromUtf8(data);
+
+ QQmlScript::Parser::JavaScriptMetaData metadata =
+ QQmlScript::Parser::extractMetaData(m_source);
+
+ m_imports.setBaseUrl(finalUrl(), finalUrlString());
+
+ m_pragmas = metadata.pragmas;
+
+ foreach (const QQmlScript::Import &import, metadata.imports) {
+ Q_ASSERT(import.type != QQmlScript::Import::File);
+
+ if (import.type == QQmlScript::Import::Script) {
+ QUrl scriptUrl = finalUrl().resolved(QUrl(import.uri));
+ QQmlScriptBlob *blob = typeLoader()->getScript(scriptUrl);
+ addDependency(blob);
+
+ ScriptReference ref;
+ ref.location = import.location.start;
+ ref.qualifier = import.qualifier;
+ ref.script = blob;
+ m_scripts << ref;
+ } else {
+ Q_ASSERT(import.type == QQmlScript::Import::Library);
+ int vmaj = -1;
+ int vmin = -1;
+ import.extractVersion(&vmaj, &vmin);
+
+ QList<QQmlError> errors;
+ if (!m_imports.addImport(importDatabase, import.uri, import.qualifier, vmaj, vmin,
+ import.type, QQmlDirComponents(), &errors)) {
+ QQmlError error = errors.takeFirst();
+ // description should be set by addImport().
+ error.setUrl(m_imports.baseUrl());
+ error.setLine(import.location.start.line);
+ error.setColumn(import.location.start.column);
+ errors.prepend(error);
+
+ setError(errors);
+ return;
+ }
+ }
+ }
+}
+
+void QQmlScriptBlob::done()
+{
+ // Check all script dependencies for errors
+ for (int ii = 0; !isError() && ii < m_scripts.count(); ++ii) {
+ const ScriptReference &script = m_scripts.at(ii);
+ Q_ASSERT(script.script->isCompleteOrError());
+ if (script.script->isError()) {
+ QList<QQmlError> errors = script.script->errors();
+ QQmlError error;
+ error.setUrl(finalUrl());
+ error.setLine(script.location.line);
+ error.setColumn(script.location.column);
+ error.setDescription(typeLoader()->tr("Script %1 unavailable").arg(script.script->url().toString()));
+ errors.prepend(error);
+ setError(errors);
+ }
+ }
+
+ if (isError())
+ return;
+
+ QQmlEngine *engine = typeLoader()->engine();
+ m_scriptData = new QQmlScriptData();
+ m_scriptData->url = finalUrl();
+ m_scriptData->urlString = finalUrlString();
+ m_scriptData->importCache = new QQmlTypeNameCache();
+
+ for (int ii = 0; !isError() && ii < m_scripts.count(); ++ii) {
+ const ScriptReference &script = m_scripts.at(ii);
+
+ m_scriptData->scripts.append(script.script);
+ m_scriptData->importCache->add(script.qualifier, ii);
+ }
+
+ m_imports.populateCache(m_scriptData->importCache, engine);
+
+ m_scriptData->pragmas = m_pragmas;
+ m_scriptData->m_programSource = m_source.toUtf8();
+ m_source.clear();
+}
+
+QQmlQmldirData::QQmlQmldirData(const QUrl &url)
+: QQmlDataBlob(url, QmldirFile)
+{
+}
+
+const QQmlDirComponents &QQmlQmldirData::dirComponents() const
+{
+ return m_components;
+}
+
+void QQmlQmldirData::dataReceived(const QByteArray &data)
+{
+ QQmlDirParser parser;
+ parser.setSource(QString::fromUtf8(data));
+ parser.parse();
+ m_components = parser.components();
+}
+
+QT_END_NAMESPACE
+
+#include "qqmltypeloader.moc"