/****************************************************************************
**
-** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
-** All rights reserved.
-** Contact: Nokia Corporation (qt-info@nokia.com)
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
**
** This file is part of the QtDeclarative module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
-** No Commercial Usage
-** This file contains pre-release code and may not be distributed.
-** You may use this file in accordance with the terms and conditions
-** contained in the Technology Preview License Agreement accompanying
-** this package.
-**
** GNU Lesser General Public License Usage
-** Alternatively, 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.
+** 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
+** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
-** If you have questions regarding the use of this file, please contact
-** Nokia at qt-info@nokia.com.
-**
+** 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.
**
**
**
#include "qdeclarativetypeloader_p.h"
#include <private/qdeclarativeengine_p.h>
+#include <private/qdeclarativeglobal_p.h>
+#include <private/qdeclarativethread_p.h>
#include <private/qdeclarativecompiler_p.h>
#include <private/qdeclarativecomponent_p.h>
-#include <private/qdeclarativeglobal_p.h>
#include <private/qdeclarativedebugtrace_p.h>
-#include <QtDeclarative/qdeclarativecomponent.h>
-#include <QtCore/qdebug.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 <QtDeclarative/qdeclarativecomponent.h>
+#include <QtDeclarative/qdeclarativeextensioninterface.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("QDeclarativeDataLoader: Caller not in main thread"); } while(false)
+#define ASSERT_LOADTHREAD() do { if(!m_thread->isThisThread()) qFatal("QDeclarativeDataLoader: Caller not in load thread"); } while(false)
+#define ASSERT_CALLBACK() do { if(!m_manager || !m_manager->m_thread->isThisThread()) qFatal("QDeclarativeDataBlob: 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 QDeclarativeDataLoader 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 QDeclarativeDataLoaderNetworkReplyProxy : public QObject
+{
+ Q_OBJECT
+public:
+ QDeclarativeDataLoaderNetworkReplyProxy(QDeclarativeDataLoader *l);
+
+public slots:
+ void finished();
+ void downloadProgress(qint64, qint64);
+
+private:
+ QDeclarativeDataLoader *l;
+};
+
+class QDeclarativeDataLoaderThread : public QDeclarativeThread
+{
+ typedef QDeclarativeDataLoaderThread This;
+
+public:
+ QDeclarativeDataLoaderThread(QDeclarativeDataLoader *loader);
+ QNetworkAccessManager *networkAccessManager() const;
+ QDeclarativeDataLoaderNetworkReplyProxy *networkReplyProxy() const;
+
+ void load(QDeclarativeDataBlob *b);
+ void loadAsync(QDeclarativeDataBlob *b);
+ void loadWithStaticData(QDeclarativeDataBlob *b, const QByteArray &);
+ void loadWithStaticDataAsync(QDeclarativeDataBlob *b, const QByteArray &);
+ void callCompleted(QDeclarativeDataBlob *b);
+ void callDownloadProgressChanged(QDeclarativeDataBlob *b, qreal p);
+ void initializeEngine(QDeclarativeExtensionInterface *, const char *);
+
+protected:
+ virtual void shutdownThread();
+
+private:
+ void loadThread(QDeclarativeDataBlob *b);
+ void loadWithStaticDataThread(QDeclarativeDataBlob *b, const QByteArray &);
+ void callCompletedMain(QDeclarativeDataBlob *b);
+ void callDownloadProgressChangedMain(QDeclarativeDataBlob *b, qreal p);
+ void initializeEngineMain(QDeclarativeExtensionInterface *iface, const char *uri);
+
+ QDeclarativeDataLoader *m_loader;
+ mutable QNetworkAccessManager *m_networkAccessManager;
+ mutable QDeclarativeDataLoaderNetworkReplyProxy *m_networkReplyProxy;
+};
+
+
+QDeclarativeDataLoaderNetworkReplyProxy::QDeclarativeDataLoaderNetworkReplyProxy(QDeclarativeDataLoader *l)
+: l(l)
+{
+}
+
+void QDeclarativeDataLoaderNetworkReplyProxy::finished()
+{
+ Q_ASSERT(sender());
+ Q_ASSERT(qobject_cast<QNetworkReply *>(sender()));
+ QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
+ l->networkReplyFinished(reply);
+}
+
+void QDeclarativeDataLoaderNetworkReplyProxy::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 QDeclarativeDataBlob
\brief The QDeclarativeDataBlob encapsulates a data request that can be issued to a QDeclarativeDataLoader.
Create a new QDeclarativeDataBlob for \a url and of the provided \a type.
*/
QDeclarativeDataBlob::QDeclarativeDataBlob(const QUrl &url, Type type)
-: m_type(type), m_status(Null), m_progress(0), m_url(url), m_finalUrl(url), m_manager(0),
- m_redirectCount(0), m_inCallback(false), m_isDone(false)
+: m_type(type), m_url(url), m_finalUrl(url), m_manager(0), m_redirectCount(0),
+ m_inCallback(false), m_isDone(false)
{
}
*/
QDeclarativeDataBlob::Status QDeclarativeDataBlob::status() const
{
- return m_status;
+ return m_data.status();
}
/*!
*/
bool QDeclarativeDataBlob::isNull() const
{
- return m_status == Null;
+ return status() == Null;
}
/*!
*/
bool QDeclarativeDataBlob::isLoading() const
{
- return m_status == Loading;
+ return status() == Loading;
}
/*!
*/
bool QDeclarativeDataBlob::isWaiting() const
{
- return m_status == WaitingForDependencies;
+ return status() == WaitingForDependencies;
}
/*!
*/
bool QDeclarativeDataBlob::isComplete() const
{
- return m_status == Complete;
+ return status() == Complete;
}
/*!
*/
bool QDeclarativeDataBlob::isError() const
{
- return m_status == Error;
+ return status() == Error;
}
/*!
*/
bool QDeclarativeDataBlob::isCompleteOrError() const
{
- return isComplete() || isError();
+ Status s = status();
+ return s == Error || s == Complete;
}
/*!
*/
qreal QDeclarativeDataBlob::progress() const
{
- return m_progress;
+ quint8 p = m_data.progress();
+ if (p == 0xFF) return 1.;
+ else return qreal(p) / qreal(0xFF);
}
/*!
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 QDeclarativeDataBlob::finalUrl() const
{
+ Q_ASSERT(isCompleteOrError() || (m_manager && m_manager->m_thread->isThisThread()));
return m_finalUrl;
}
/*!
Return the errors on this blob.
+
+May only be called from the load thread, or after the blob isCompleteOrError().
*/
QList<QDeclarativeError> QDeclarativeDataBlob::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, although you can change the
-specific errors by additional calls to setError.
+will be ignored. Entry into the Error state is irreversable.
+
+The setError() method may only be called from within a QDeclarativeDataBlob callback.
*/
void QDeclarativeDataBlob::setError(const QDeclarativeError &errors)
{
+ ASSERT_CALLBACK();
+
QList<QDeclarativeError> l;
l << errors;
setError(l);
*/
void QDeclarativeDataBlob::setError(const QList<QDeclarativeError> &errors)
{
- m_status = Error;
- m_errors = 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();
/*!
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 QDeclarativeDataBlob callback.
*/
void QDeclarativeDataBlob::addDependency(QDeclarativeDataBlob *blob)
{
+ ASSERT_CALLBACK();
+
Q_ASSERT(status() != Null);
if (!blob ||
blob->status() == Error || blob->status() == Complete ||
- status() == Error || status() == Complete ||
+ status() == Error || status() == Complete || m_isDone ||
m_waitingFor.contains(blob))
return;
blob->addref();
- m_status = WaitingForDependencies;
+
+ m_data.setStatus(WaitingForDependencies);
+
m_waitingFor.append(blob);
blob->m_waitingOnMe.append(this);
}
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 QDeclarativeDataBlob::done()
{
/*!
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 QDeclarativeDataBlob::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 QDeclarativeDataBlob::completed()
+{
+}
+
+
void QDeclarativeDataBlob::tryDone()
{
if (status() != Loading && m_waitingFor.isEmpty() && !m_isDone) {
- if (status() != Error)
- m_status = Complete;
-
m_isDone = true;
+ addref();
+
+#ifdef DATABLOB_DEBUG
+ qWarning("QDeclarativeDataBlob::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("QDeclarativeDataBlob: Dispatching completed");
+#endif
+ m_manager->m_thread->callCompleted(this);
+ }
+
+ release();
}
}
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
+
+QDeclarativeDataBlob::ThreadData::ThreadData()
+: _p(0)
+{
+}
+
+QDeclarativeDataBlob::Status QDeclarativeDataBlob::ThreadData::status() const
+{
+ return QDeclarativeDataBlob::Status((_p.load() & TD_STATUS_MASK) >> TD_STATUS_SHIFT);
+}
+
+void QDeclarativeDataBlob::ThreadData::setStatus(QDeclarativeDataBlob::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 QDeclarativeDataBlob::ThreadData::isAsync() const
+{
+ return _p.load() & TD_ASYNC_MASK;
+}
+
+void QDeclarativeDataBlob::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 QDeclarativeDataBlob::ThreadData::progress() const
+{
+ return quint8((_p.load() & TD_PROGRESS_MASK) >> TD_PROGRESS_SHIFT);
+}
+
+void QDeclarativeDataBlob::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;
+ }
+}
+
+QDeclarativeDataLoaderThread::QDeclarativeDataLoaderThread(QDeclarativeDataLoader *loader)
+: m_loader(loader), m_networkAccessManager(0), m_networkReplyProxy(0)
+{
+}
+
+QNetworkAccessManager *QDeclarativeDataLoaderThread::networkAccessManager() const
+{
+ Q_ASSERT(isThisThread());
+ if (!m_networkAccessManager) {
+ m_networkAccessManager = QDeclarativeEnginePrivate::get(m_loader->engine())->createNetworkAccessManager(0);
+ m_networkReplyProxy = new QDeclarativeDataLoaderNetworkReplyProxy(m_loader);
+ }
+
+ return m_networkAccessManager;
+}
+
+QDeclarativeDataLoaderNetworkReplyProxy *QDeclarativeDataLoaderThread::networkReplyProxy() const
+{
+ Q_ASSERT(isThisThread());
+ Q_ASSERT(m_networkReplyProxy); // Must call networkAccessManager() first
+ return m_networkReplyProxy;
+}
+
+void QDeclarativeDataLoaderThread::load(QDeclarativeDataBlob *b)
+{
+ b->addref();
+ callMethodInThread(&This::loadThread, b);
+}
+
+void QDeclarativeDataLoaderThread::loadAsync(QDeclarativeDataBlob *b)
+{
+ b->addref();
+ postMethodToThread(&This::loadThread, b);
+}
+
+void QDeclarativeDataLoaderThread::loadWithStaticData(QDeclarativeDataBlob *b, const QByteArray &d)
+{
+ b->addref();
+ callMethodInThread(&This::loadWithStaticDataThread, b, d);
+}
+
+void QDeclarativeDataLoaderThread::loadWithStaticDataAsync(QDeclarativeDataBlob *b, const QByteArray &d)
+{
+ b->addref();
+ postMethodToThread(&This::loadWithStaticDataThread, b, d);
+}
+
+void QDeclarativeDataLoaderThread::callCompleted(QDeclarativeDataBlob *b)
+{
+ b->addref();
+ postMethodToMain(&This::callCompletedMain, b);
+}
+
+void QDeclarativeDataLoaderThread::callDownloadProgressChanged(QDeclarativeDataBlob *b, qreal p)
+{
+ b->addref();
+ postMethodToMain(&This::callDownloadProgressChangedMain, b, p);
+}
+
+void QDeclarativeDataLoaderThread::initializeEngine(QDeclarativeExtensionInterface *iface,
+ const char *uri)
+{
+ callMethodInMain(&This::initializeEngineMain, iface, uri);
+}
+
+void QDeclarativeDataLoaderThread::shutdownThread()
+{
+ delete m_networkAccessManager;
+ m_networkAccessManager = 0;
+ delete m_networkReplyProxy;
+ m_networkReplyProxy = 0;
+}
+
+void QDeclarativeDataLoaderThread::loadThread(QDeclarativeDataBlob *b)
+{
+ m_loader->loadThread(b);
+ b->release();
+}
+
+void QDeclarativeDataLoaderThread::loadWithStaticDataThread(QDeclarativeDataBlob *b, const QByteArray &d)
+{
+ m_loader->loadWithStaticDataThread(b, d);
+ b->release();
+}
+
+void QDeclarativeDataLoaderThread::callCompletedMain(QDeclarativeDataBlob *b)
+{
+#ifdef DATABLOB_DEBUG
+ qWarning("QDeclarativeDataLoaderThread: %s completed() callback", qPrintable(b->url().toString()));
+#endif
+ b->completed();
+ b->release();
+}
+
+void QDeclarativeDataLoaderThread::callDownloadProgressChangedMain(QDeclarativeDataBlob *b, qreal p)
+{
+#ifdef DATABLOB_DEBUG
+ qWarning("QDeclarativeDataLoaderThread: %s downloadProgressChanged(%f) callback",
+ qPrintable(b->url().toString()), p);
+#endif
+ b->downloadProgressChanged(p);
+ b->release();
+}
+
+void QDeclarativeDataLoaderThread::initializeEngineMain(QDeclarativeExtensionInterface *iface,
+ const char *uri)
+{
+ Q_ASSERT(m_loader->engine()->thread() == QThread::currentThread());
+ iface->initializeEngine(m_loader->engine(), uri);
+}
+
/*!
\class QDeclarativeDataLoader
-\brief The QDeclarativeDataLoader class abstracts loading files and their dependecies over the network.
+\brief The QDeclarativeDataLoader class abstracts loading files and their dependencies over the network.
\internal
The QDeclarativeDataLoader class is provided for the exclusive use of the QDeclarativeTypeLoader class.
To complete processing, the QDeclarativeDataBlob::done() callback is invoked. done() is called when
one of these three preconditions are met.
-1. The QDeclarativeDataBlob has no dependencies.
-2. The QDeclarativeDataBlob has an error set.
-3. All the QDeclarativeDataBlob's dependencies are themselves "done()".
+\list 1
+\o The QDeclarativeDataBlob has no dependencies.
+\o The QDeclarativeDataBlob has an error set.
+\o All the QDeclarativeDataBlob's dependencies are themselves "done()".
+\endlist
Thus QDeclarativeDataBlob::done() will always eventually be called, even if the blob has an error set.
*/
Create a new QDeclarativeDataLoader for \a engine.
*/
QDeclarativeDataLoader::QDeclarativeDataLoader(QDeclarativeEngine *engine)
-: m_engine(engine)
+: m_engine(engine), m_thread(new QDeclarativeDataLoaderThread(this))
{
}
{
for (NetworkReplies::Iterator iter = m_networkReplies.begin(); iter != m_networkReplies.end(); ++iter)
(*iter)->release();
+
+ m_thread->shutdown();
+ delete m_thread;
+}
+
+void QDeclarativeDataLoader::lock()
+{
+ m_thread->lock();
+}
+
+void QDeclarativeDataLoader::unlock()
+{
+ m_thread->unlock();
}
/*!
Load the provided \a blob from the network or filesystem.
+
+The loader must be locked.
+*/
+void QDeclarativeDataLoader::load(QDeclarativeDataBlob *blob, Mode mode)
+{
+#ifdef DATABLOB_DEBUG
+ qWarning("QDeclarativeDataLoader::load(%s): %s thread", qPrintable(blob->m_url.toString()),
+ m_thread->isThisThread()?"Compile":"Engine");
+#endif
+
+ Q_ASSERT(blob->status() == QDeclarativeDataBlob::Null);
+ Q_ASSERT(blob->m_manager == 0);
+
+ blob->m_data.setStatus(QDeclarativeDataBlob::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 QDeclarativeDataLoader::load(QDeclarativeDataBlob *blob)
+void QDeclarativeDataLoader::loadWithStaticData(QDeclarativeDataBlob *blob, const QByteArray &data, Mode mode)
{
+#ifdef DATABLOB_DEBUG
+ qWarning("QDeclarativeDataLoader::loadWithStaticData(%s, data): %s thread", qPrintable(blob->m_url.toString()),
+ m_thread->isThisThread()?"Compile":"Engine");
+#endif
+
Q_ASSERT(blob->status() == QDeclarativeDataBlob::Null);
Q_ASSERT(blob->m_manager == 0);
+
+ blob->m_data.setStatus(QDeclarativeDataBlob::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 QDeclarativeDataLoader::loadWithStaticDataThread(QDeclarativeDataBlob *blob, const QByteArray &data)
+{
+ ASSERT_LOADTHREAD();
- blob->m_status = QDeclarativeDataBlob::Loading;
+ setData(blob, data);
+}
+
+void QDeclarativeDataLoader::loadThread(QDeclarativeDataBlob *blob)
+{
+ ASSERT_LOADTHREAD();
if (blob->m_url.isEmpty()) {
QDeclarativeError error;
if (file.open(QFile::ReadOnly)) {
QByteArray data = file.readAll();
- blob->m_progress = 1.;
- blob->downloadProgressChanged(1.);
+ blob->m_data.setProgress(0xFF);
+ if (blob->m_data.isAsync())
+ m_thread->callDownloadProgressChanged(blob, 1.);
setData(blob, data);
} else {
} else {
- blob->m_manager = this;
- QNetworkReply *reply = m_engine->networkAccessManager()->get(QNetworkRequest(blob->m_url));
+ QNetworkReply *reply = m_thread->networkAccessManager()->get(QNetworkRequest(blob->m_url));
+ QObject *nrp = m_thread->networkReplyProxy();
QObject::connect(reply, SIGNAL(downloadProgress(qint64,qint64)),
- this, SLOT(networkReplyProgress(qint64,qint64)));
+ nrp, SLOT(downloadProgress(qint64,qint64)));
QObject::connect(reply, SIGNAL(finished()),
- this, SLOT(networkReplyFinished()));
+ nrp, SLOT(finished()));
m_networkReplies.insert(reply, blob);
blob->addref();
#define DATALOADER_MAXIMUM_REDIRECT_RECURSION 16
-void QDeclarativeDataLoader::networkReplyFinished()
+void QDeclarativeDataLoader::networkReplyFinished(QNetworkReply *reply)
{
- QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
+ Q_ASSERT(m_thread->isThisThread());
+
reply->deleteLater();
QDeclarativeDataBlob *blob = m_networkReplies.take(reply);
QUrl url = reply->url().resolved(redirect.toUrl());
blob->m_finalUrl = url;
- QNetworkReply *reply = m_engine->networkAccessManager()->get(QNetworkRequest(url));
- QObject::connect(reply, SIGNAL(finished()), this, SLOT(networkReplyFinished()));
+ 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;
}
blob->release();
}
-void QDeclarativeDataLoader::networkReplyProgress(qint64 bytesReceived, qint64 bytesTotal)
+void QDeclarativeDataLoader::networkReplyProgress(QNetworkReply *reply,
+ qint64 bytesReceived, qint64 bytesTotal)
{
- QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
+ Q_ASSERT(m_thread->isThisThread());
+
QDeclarativeDataBlob *blob = m_networkReplies.value(reply);
Q_ASSERT(blob);
if (bytesTotal != 0) {
- blob->m_progress = bytesReceived / bytesTotal;
- blob->downloadProgressChanged(blob->m_progress);
+ 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());
}
}
/*!
-Load the provided \a blob with \a data. The blob's URL is not used by the data loader in this case.
+Return the QDeclarativeEngine associated with this loader
*/
-void QDeclarativeDataLoader::loadWithStaticData(QDeclarativeDataBlob *blob, const QByteArray &data)
+QDeclarativeEngine *QDeclarativeDataLoader::engine() const
{
- Q_ASSERT(blob->status() == QDeclarativeDataBlob::Null);
- Q_ASSERT(blob->m_manager == 0);
-
- blob->m_status = QDeclarativeDataBlob::Loading;
-
- setData(blob, data);
+ return m_engine;
}
/*!
-Return the QDeclarativeEngine associated with this loader
+Call the initializeEngine() method on \a iface. Used by QDeclarativeImportDatabase to ensure it
+gets called in the correct thread.
*/
-QDeclarativeEngine *QDeclarativeDataLoader::engine() const
+void QDeclarativeDataLoader::initializeEngine(QDeclarativeExtensionInterface *iface,
+ const char *uri)
{
- return m_engine;
+ 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 QDeclarativeDataLoader::setData(QDeclarativeDataBlob *blob, const QByteArray &data)
{
blob->m_inCallback = true;
blob->allDependenciesDone();
if (blob->status() != QDeclarativeDataBlob::Error)
- blob->m_status = QDeclarativeDataBlob::WaitingForDependencies;
+ blob->m_data.setStatus(QDeclarativeDataBlob::WaitingForDependencies);
blob->m_inCallback = false;
}
/*!
-\class QDeclarativeTypeLoader
+Constructs a new type loader that uses the given \a engine.
*/
QDeclarativeTypeLoader::QDeclarativeTypeLoader(QDeclarativeEngine *engine)
: QDeclarativeDataLoader(engine)
{
}
+/*!
+Destroys the type loader, first clearing the cache of any information about
+loaded files.
+*/
QDeclarativeTypeLoader::~QDeclarativeTypeLoader()
{
clearCache();
(QDeclarativeEnginePrivate::urlToLocalFileOrQrc(url).isEmpty() ||
!QDir::isRelativePath(QDeclarativeEnginePrivate::urlToLocalFileOrQrc(url))));
+ lock();
+
QDeclarativeTypeData *typeData = m_typeCache.value(url);
if (!typeData) {
}
typeData->addref();
+
+ unlock();
+
return typeData;
}
*/
QDeclarativeTypeData *QDeclarativeTypeLoader::get(const QByteArray &data, const QUrl &url, Options options)
{
+ lock();
+
QDeclarativeTypeData *typeData = new QDeclarativeTypeData(url, options, this);
QDeclarativeDataLoader::loadWithStaticData(typeData, data);
+
+ unlock();
+
return typeData;
}
/*!
-Return a QDeclarativeScriptData for \a url. The QDeclarativeScriptData may be cached.
+Return a QDeclarativeScriptBlob for \a url. The QDeclarativeScriptData may be cached.
*/
-QDeclarativeScriptData *QDeclarativeTypeLoader::getScript(const QUrl &url)
+QDeclarativeScriptBlob *QDeclarativeTypeLoader::getScript(const QUrl &url)
{
Q_ASSERT(!url.isRelative() &&
(QDeclarativeEnginePrivate::urlToLocalFileOrQrc(url).isEmpty() ||
!QDir::isRelativePath(QDeclarativeEnginePrivate::urlToLocalFileOrQrc(url))));
- QDeclarativeScriptData *scriptData = m_scriptCache.value(url);
+ lock();
- if (!scriptData) {
- scriptData = new QDeclarativeScriptData(url);
- m_scriptCache.insert(url, scriptData);
- QDeclarativeDataLoader::load(scriptData);
+ QDeclarativeScriptBlob *scriptBlob = m_scriptCache.value(url);
+
+ if (!scriptBlob) {
+ scriptBlob = new QDeclarativeScriptBlob(url, this);
+ m_scriptCache.insert(url, scriptBlob);
+ QDeclarativeDataLoader::load(scriptBlob);
}
- scriptData->addref();
- return scriptData;
+ scriptBlob->addref();
+
+ unlock();
+
+ return scriptBlob;
}
/*!
-Return a QDeclarativeQmldirData for \a url. The QDeclarativeQmldirData may be cached.
+Returns a QDeclarativeQmldirData for \a url. The QDeclarativeQmldirData may be cached.
*/
QDeclarativeQmldirData *QDeclarativeTypeLoader::getQmldir(const QUrl &url)
{
(QDeclarativeEnginePrivate::urlToLocalFileOrQrc(url).isEmpty() ||
!QDir::isRelativePath(QDeclarativeEnginePrivate::urlToLocalFileOrQrc(url))));
+ lock();
+
QDeclarativeQmldirData *qmldirData = m_qmldirCache.value(url);
if (!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 QDeclarativeTypeLoader::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 QDeclarativeTypeLoader::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 QDeclarativeDirParser for absoluteFilePath. The QDeclarativeDirParser may be cached.
+*/
+const QDeclarativeDirParser *QDeclarativeTypeLoader::qmlDirParser(const QString &absoluteFilePath)
+{
+ QDeclarativeDirParser *qmldirParser;
+ QDeclarativeDirParser **val = m_importQmlDirCache.value(absoluteFilePath);
+ if (!val) {
+ qmldirParser = new QDeclarativeDirParser;
+ 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 QDeclarativeTypeLoader::clearCache()
{
for (TypeCache::Iterator iter = m_typeCache.begin(); iter != m_typeCache.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();
}
QDeclarativeTypeData::QDeclarativeTypeData(const QUrl &url, QDeclarativeTypeLoader::Options options,
QDeclarativeTypeLoader *manager)
-: QDeclarativeDataBlob(url, QmlFile), m_options(options), m_typesResolved(false),
- m_compiledData(0), m_typeLoader(manager)
+: QDeclarativeDataBlob(url, QmlFile), m_options(options), m_imports(manager), m_typesResolved(false),
+ m_compiledData(0), m_typeLoader(manager)
{
}
return m_imports;
}
-const QDeclarativeScriptParser &QDeclarativeTypeData::parser() const
+const QDeclarativeScript::Parser &QDeclarativeTypeData::parser() const
{
return scriptParser;
}
return m_scripts;
}
+const QSet<QString> &QDeclarativeTypeData::namespaces() const
+{
+ return m_namespaces;
+}
+
QDeclarativeCompiledData *QDeclarativeTypeData::compiledData() const
{
if (m_compiledData)
void QDeclarativeTypeData::done()
{
- addref();
-
// Check all script dependencies for errors
for (int ii = 0; !isError() && ii < m_scripts.count(); ++ii) {
const ScriptReference &script = m_scripts.at(ii);
if (!(m_options & QDeclarativeTypeLoader::PreserveParser))
scriptParser.clear();
+}
+void QDeclarativeTypeData::completed()
+{
// Notify callbacks
while (!m_callbacks.isEmpty()) {
TypeDataCallback *callback = m_callbacks.takeFirst();
callback->typeDataReady(this);
}
-
- release();
}
void QDeclarativeTypeData::dataReceived(const QByteArray &data)
m_imports.setBaseUrl(finalUrl());
- foreach (const QDeclarativeScriptParser::Import &import, scriptParser.imports()) {
- if (import.type == QDeclarativeScriptParser::Import::File && import.qualifier.isEmpty()) {
+ foreach (const QDeclarativeScript::Import &import, scriptParser.imports()) {
+ if (import.type == QDeclarativeScript::Import::File && import.qualifier.isEmpty()) {
QUrl importUrl = finalUrl().resolved(QUrl(import.uri + QLatin1String("/qmldir")));
if (QDeclarativeEnginePrivate::urlToLocalFileOrQrc(importUrl).isEmpty()) {
QDeclarativeQmldirData *data = typeLoader()->getQmldir(importUrl);
addDependency(data);
m_qmldirs << data;
}
- } else if (import.type == QDeclarativeScriptParser::Import::Script) {
+ } else if (import.type == QDeclarativeScript::Import::Script) {
QUrl scriptUrl = finalUrl().resolved(QUrl(import.uri));
- QDeclarativeScriptData *data = typeLoader()->getScript(scriptUrl);
- addDependency(data);
+ QDeclarativeScriptBlob *blob = typeLoader()->getScript(scriptUrl);
+ addDependency(blob);
ScriptReference ref;
ref.location = import.location.start;
ref.qualifier = import.qualifier;
- ref.script = data;
+ ref.script = blob;
m_scripts << ref;
}
m_compiledData->name = m_compiledData->url.toString();
QDeclarativeDebugTrace::rangeData(QDeclarativeDebugTrace::Compiling, m_compiledData->name);
- QDeclarativeCompiler compiler;
+ QDeclarativeCompiler compiler(&scriptParser._pool);
if (!compiler.compile(typeLoader()->engine(), this, m_compiledData)) {
setError(compiler.errors());
m_compiledData->release();
// 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<QDeclarativeError> errors;
if (QDeclarativeQmldirData *qmldir = qmldirForUrl(finalUrl().resolved(QUrl(QLatin1String("./qmldir"))))) {
m_imports.addImport(importDatabase, QLatin1String("."),
- QString(), -1, -1, QDeclarativeScriptParser::Import::File,
- qmldir->dirComponents(), 0);
+ QString(), -1, -1, QDeclarativeScript::Import::File,
+ qmldir->dirComponents(), &errors);
} else {
m_imports.addImport(importDatabase, QLatin1String("."),
- QString(), -1, -1, QDeclarativeScriptParser::Import::File,
- QDeclarativeDirComponents(), 0);
+ QString(), -1, -1, QDeclarativeScript::Import::File,
+ QDeclarativeDirComponents(), &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<QDeclarativeError> realErrors;
+ for (int i = 0; i < errors.size(); ++i) {
+ if (errors.at(i).description() != QDeclarativeImportDatabase::tr("import \".\" has no qmldir and no namespace")
+ && errors.at(i).description() != QDeclarativeImportDatabase::tr("\".\": no such directory")) {
+ realErrors.prepend(errors.at(i)); // this is a real error.
+ }
}
- foreach (const QDeclarativeScriptParser::Import &import, scriptParser.imports()) {
+ // report any real errors which occurred during plugin loading or qmldir parsing.
+ if (!realErrors.isEmpty()) {
+ setError(realErrors);
+ return;
+ }
+
+ foreach (const QDeclarativeScript::Import &import, scriptParser.imports()) {
QDeclarativeDirComponents qmldircomponentsnetwork;
- if (import.type == QDeclarativeScriptParser::Import::Script)
+ if (import.type == QDeclarativeScript::Import::Script)
continue;
- if (import.type == QDeclarativeScriptParser::Import::File && import.qualifier.isEmpty()) {
+ if (import.type == QDeclarativeScript::Import::File && import.qualifier.isEmpty()) {
QUrl qmldirUrl = finalUrl().resolved(QUrl(import.uri + QLatin1String("/qmldir")));
- if (QDeclarativeQmldirData *qmldir = qmldirForUrl(qmldirUrl))
+ if (QDeclarativeQmldirData *qmldir = qmldirForUrl(qmldirUrl))
qmldircomponentsnetwork = qmldir->dirComponents();
}
int vmaj = -1;
int vmin = -1;
+ import.extractVersion(&vmaj, &vmin);
- if (!import.version.isEmpty()) {
- int dot = import.version.indexOf(QLatin1Char('.'));
- if (dot < 0) {
- vmaj = import.version.toInt();
- vmin = 0;
- } else {
- vmaj = import.version.left(dot).toInt();
- vmin = import.version.mid(dot+1).toInt();
- }
- }
-
- QString errorString;
+ QList<QDeclarativeError> errors;
if (!m_imports.addImport(importDatabase, import.uri, import.qualifier,
- vmaj, vmin, import.type, qmldircomponentsnetwork, &errorString)) {
+ vmaj, vmin, import.type, qmldircomponentsnetwork, &errors)) {
QDeclarativeError error;
+ if (errors.size()) {
+ error = errors.takeFirst();
+ } else {
+ // this should not be possible!
+ // Description should come from error provided by addImport() function.
+ error.setDescription(QDeclarativeTypeLoader::tr("Unreported error adding script import to import database"));
+ }
error.setUrl(m_imports.baseUrl());
- error.setDescription(errorString);
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(error);
+ setError(errors);
return;
}
}
- foreach (QDeclarativeScriptParser::TypeReference *parserRef, scriptParser.referencedTypes()) {
- QByteArray typeName = parserRef->name.toUtf8();
+ // Add any imported scripts to our resolved set
+ foreach (const QDeclarativeImports::ScriptReference &script, m_imports.resolvedScripts())
+ {
+ QDeclarativeScriptBlob *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 (QDeclarativeScript::TypeReference *parserRef, scriptParser.referencedTypes()) {
TypeReference ref;
- QUrl url;
+ QString url;
int majorVersion;
int minorVersion;
QDeclarativeImportedNamespace *typeNamespace = 0;
- QString errorString;
+ QList<QDeclarativeError> errors;
- if (!m_imports.resolveType(typeName, &ref.type, &url, &majorVersion, &minorVersion,
- &typeNamespace, &errorString) || typeNamespace) {
+ 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 {})
QDeclarativeError error;
- error.setUrl(m_imports.baseUrl());
QString userTypeName = parserRef->name;
userTypeName.replace(QLatin1Char('/'),QLatin1Char('.'));
- if (typeNamespace)
+ if (typeNamespace) {
error.setDescription(QDeclarativeTypeLoader::tr("Namespace %1 cannot be used as a type").arg(userTypeName));
- else
- error.setDescription(QDeclarativeTypeLoader::tr("%1 %2").arg(userTypeName).arg(errorString));
+ } else {
+ if (errors.size()) {
+ error = errors.takeFirst();
+ } else {
+ // this should not be possible!
+ // Description should come from error provided by addImport() function.
+ error.setDescription(QDeclarativeTypeLoader::tr("Unreported error adding script import to import database"));
+ }
+ error.setUrl(m_imports.baseUrl());
+ error.setDescription(QDeclarativeTypeLoader::tr("%1 %2").arg(userTypeName).arg(error.description()));
+ }
if (!parserRef->refObjects.isEmpty()) {
- QDeclarativeParser::Object *obj = parserRef->refObjects.first();
+ QDeclarativeScript::Object *obj = parserRef->refObjects.first();
error.setLine(obj->location.start.line);
error.setColumn(obj->location.start.column);
}
-
- setError(error);
+
+ errors.prepend(error);
+ setError(errors);
return;
}
if (ref.type) {
ref.majorVersion = majorVersion;
ref.minorVersion = minorVersion;
- foreach (QDeclarativeParser::Object *obj, parserRef->refObjects) {
- // store namespace for DOM
- obj->majorVersion = majorVersion;
- obj->minorVersion = minorVersion;
- }
} else {
- ref.typeData = typeLoader()->get(url);
+ ref.typeData = typeLoader()->get(QUrl(url));
addDependency(ref.typeData);
}
return 0;
}
-QDeclarativeScriptData::QDeclarativeScriptData(const QUrl &url)
-: QDeclarativeDataBlob(url, JavaScriptFile), m_pragmas(QDeclarativeParser::Object::ScriptBlock::None)
+QDeclarativeScriptData::QDeclarativeScriptData()
+: importCache(0), pragmas(QDeclarativeScript::Object::ScriptBlock::None), m_loaded(false)
+{
+}
+
+QDeclarativeScriptData::~QDeclarativeScriptData()
+{
+}
+
+void QDeclarativeScriptData::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 QDeclarativeCleanup was added to the engine.
+ release();
+}
+
+QDeclarativeScriptBlob::QDeclarativeScriptBlob(const QUrl &url, QDeclarativeTypeLoader *loader)
+: QDeclarativeDataBlob(url, JavaScriptFile), m_pragmas(QDeclarativeScript::Object::ScriptBlock::None),
+ m_imports(loader), m_scriptData(0), m_typeLoader(loader)
{
}
-QDeclarativeParser::Object::ScriptBlock::Pragmas QDeclarativeScriptData::pragmas() const
+QDeclarativeScriptBlob::~QDeclarativeScriptBlob()
+{
+ if (m_scriptData) {
+ m_scriptData->release();
+ m_scriptData = 0;
+ }
+}
+
+QDeclarativeScript::Object::ScriptBlock::Pragmas QDeclarativeScriptBlob::pragmas() const
{
return m_pragmas;
}
-QString QDeclarativeScriptData::scriptSource() const
+QString QDeclarativeScriptBlob::scriptSource() const
{
return m_source;
}
-void QDeclarativeScriptData::dataReceived(const QByteArray &data)
+QDeclarativeTypeLoader *QDeclarativeScriptBlob::typeLoader() const
+{
+ return m_typeLoader;
+}
+
+const QDeclarativeImports &QDeclarativeScriptBlob::imports() const
+{
+ return m_imports;
+}
+
+QDeclarativeScriptData *QDeclarativeScriptBlob::scriptData() const
{
+ return m_scriptData;
+}
+
+void QDeclarativeScriptBlob::dataReceived(const QByteArray &data)
+{
+ QDeclarativeEnginePrivate *ep = QDeclarativeEnginePrivate::get(m_typeLoader->engine());
+ QDeclarativeImportDatabase *importDatabase = &ep->importDatabase;
+
m_source = QString::fromUtf8(data);
- m_pragmas = QDeclarativeScriptParser::extractPragmas(m_source);
+
+ QDeclarativeScript::Parser::JavaScriptMetaData metadata =
+ QDeclarativeScript::Parser::extractMetaData(m_source);
+
+ m_imports.setBaseUrl(finalUrl());
+
+ m_pragmas = metadata.pragmas;
+
+ foreach (const QDeclarativeScript::Import &import, metadata.imports) {
+ Q_ASSERT(import.type != QDeclarativeScript::Import::File);
+
+ if (import.type == QDeclarativeScript::Import::Script) {
+ QUrl scriptUrl = finalUrl().resolved(QUrl(import.uri));
+ QDeclarativeScriptBlob *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 == QDeclarativeScript::Import::Library);
+ int vmaj = -1;
+ int vmin = -1;
+ import.extractVersion(&vmaj, &vmin);
+
+ QList<QDeclarativeError> errors;
+ if (!m_imports.addImport(importDatabase, import.uri, import.qualifier, vmaj, vmin,
+ import.type, QDeclarativeDirComponents(), &errors)) {
+ QDeclarativeError 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 QDeclarativeScriptBlob::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<QDeclarativeError> errors = script.script->errors();
+ QDeclarativeError 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;
+
+ QDeclarativeEngine *engine = typeLoader()->engine();
+ m_scriptData = new QDeclarativeScriptData();
+ m_scriptData->url = finalUrl();
+ m_scriptData->importCache = new QDeclarativeTypeNameCache();
+
+ 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;
}
QDeclarativeQmldirData::QDeclarativeQmldirData(const QUrl &url)
QT_END_NAMESPACE
+#include "qdeclarativetypeloader.moc"