/****************************************************************************
**
-** 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_END_LICENSE$
**
****************************************************************************/
#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 dependencies over the network.
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();
+
+ setData(blob, data);
+}
- blob->m_status = QDeclarativeDataBlob::Loading;
+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;
if (!blob->isError() && !blob->isWaiting())
blob->allDependenciesDone();
- if (blob->status() != QDeclarativeDataBlob::Error)
- blob->m_status = QDeclarativeDataBlob::WaitingForDependencies;
+ if (blob->status() != QDeclarativeDataBlob::Error)
+ blob->m_data.setStatus(QDeclarativeDataBlob::WaitingForDependencies);
blob->m_inCallback = false;
(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;
}
(QDeclarativeEnginePrivate::urlToLocalFileOrQrc(url).isEmpty() ||
!QDir::isRelativePath(QDeclarativeEnginePrivate::urlToLocalFileOrQrc(url))));
+ lock();
+
QDeclarativeScriptBlob *scriptBlob = m_scriptCache.value(url);
if (!scriptBlob) {
QDeclarativeDataLoader::load(scriptBlob);
}
+ scriptBlob->addref();
+
+ unlock();
+
return scriptBlob;
}
(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.
*/
(*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));
QDeclarativeScriptBlob *blob = typeLoader()->getScript(scriptUrl);
addDependency(blob);
ref.location = import.location.start;
ref.qualifier = import.qualifier;
ref.script = blob;
- blob->addref();
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();
QList<QDeclarativeError> errors;
if (QDeclarativeQmldirData *qmldir = qmldirForUrl(finalUrl().resolved(QUrl(QLatin1String("./qmldir"))))) {
m_imports.addImport(importDatabase, QLatin1String("."),
- QString(), -1, -1, QDeclarativeScriptParser::Import::File,
+ QString(), -1, -1, QDeclarativeScript::Import::File,
qmldir->dirComponents(), &errors);
} else {
m_imports.addImport(importDatabase, QLatin1String("."),
- QString(), -1, -1, QDeclarativeScriptParser::Import::File,
+ QString(), -1, -1, QDeclarativeScript::Import::File,
QDeclarativeDirComponents(), &errors);
}
return;
}
- foreach (const QDeclarativeScriptParser::Import &import, scriptParser.imports()) {
+ 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))
qmldircomponentsnetwork = qmldir->dirComponents();
}
}
- 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;
QList<QDeclarativeError> errors;
- if (!m_imports.resolveType(typeName, &ref.type, &url, &majorVersion, &minorVersion,
+ 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 {})
}
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);
}
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(QDeclarativeEngine *engine)
-: QDeclarativeCleanup(engine), importCache(0), pragmas(QDeclarativeParser::Object::ScriptBlock::None),
- m_loaded(false)
+QDeclarativeScriptData::QDeclarativeScriptData()
+: importCache(0), pragmas(QDeclarativeScript::Object::ScriptBlock::None), m_loaded(false)
{
}
QDeclarativeScriptData::~QDeclarativeScriptData()
{
- clear();
}
void QDeclarativeScriptData::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(QDeclarativeParser::Object::ScriptBlock::None),
- m_scriptData(0), m_typeLoader(loader)
+: QDeclarativeDataBlob(url, JavaScriptFile), m_pragmas(QDeclarativeScript::Object::ScriptBlock::None),
+ m_imports(loader), m_scriptData(0), m_typeLoader(loader)
{
}
}
}
-QDeclarativeParser::Object::ScriptBlock::Pragmas QDeclarativeScriptBlob::pragmas() const
+QDeclarativeScript::Object::ScriptBlock::Pragmas QDeclarativeScriptBlob::pragmas() const
{
return m_pragmas;
}
m_source = QString::fromUtf8(data);
- QDeclarativeScriptParser::JavaScriptMetaData metadata =
- QDeclarativeScriptParser::extractMetaData(m_source);
+ QDeclarativeScript::Parser::JavaScriptMetaData metadata =
+ QDeclarativeScript::Parser::extractMetaData(m_source);
m_imports.setBaseUrl(finalUrl());
m_pragmas = metadata.pragmas;
- foreach (const QDeclarativeScriptParser::Import &import, metadata.imports) {
- Q_ASSERT(import.type != QDeclarativeScriptParser::Import::File);
+ foreach (const QDeclarativeScript::Import &import, metadata.imports) {
+ Q_ASSERT(import.type != QDeclarativeScript::Import::File);
- if (import.type == QDeclarativeScriptParser::Import::Script) {
+ if (import.type == QDeclarativeScript::Import::Script) {
QUrl scriptUrl = finalUrl().resolved(QUrl(import.uri));
QDeclarativeScriptBlob *blob = typeLoader()->getScript(scriptUrl);
addDependency(blob);
ref.location = import.location.start;
ref.qualifier = import.qualifier;
ref.script = blob;
- blob->addref();
m_scripts << ref;
} else {
- Q_ASSERT(import.type == QDeclarativeScriptParser::Import::Library);
+ Q_ASSERT(import.type == QDeclarativeScript::Import::Library);
int vmaj = -1;
int vmin = -1;
import.extractVersion(&vmaj, &vmin);
return;
QDeclarativeEngine *engine = typeLoader()->engine();
- m_scriptData = new QDeclarativeScriptData(engine);
+ m_scriptData = new QDeclarativeScriptData();
m_scriptData->url = finalUrl();
- m_scriptData->importCache = new QDeclarativeTypeNameCache(engine);
+ m_scriptData->importCache = new QDeclarativeTypeNameCache();
for (int ii = 0; !isError() && ii < m_scripts.count(); ++ii) {
const ScriptReference &script = m_scripts.at(ii);
m_imports.populateCache(m_scriptData->importCache, engine);
m_scriptData->pragmas = m_pragmas;
-
- // XXX TODO: Handle errors that occur duing the script compile
- QV8Engine *v8engine = &QDeclarativeEnginePrivate::get(engine)->v8engine;
- v8::HandleScope handle_scope;
- v8::Context::Scope scope(v8engine->context());
- v8::Local<v8::Script> program = v8engine->qmlModeCompile(m_source, finalUrl().toString(), 1);
- m_scriptData->m_program = qPersistentNew<v8::Script>(program);
+ m_scriptData->m_programSource = m_source;
}
QDeclarativeQmldirData::QDeclarativeQmldirData(const QUrl &url)
QT_END_NAMESPACE
+#include "qdeclarativetypeloader.moc"