Merge branch 'master' of git://gitorious.org/qt/qtdeclarative into api_changes
[profile/ivi/qtdeclarative.git] / src / qml / qml / qqmltypeloader.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the QtQml module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
16 **
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
20 **
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
28 **
29 ** Other Usage
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qqmltypeloader_p.h"
43
44 #include <private/qqmlengine_p.h>
45 #include <private/qqmlglobal_p.h>
46 #include <private/qqmlthread_p.h>
47 #include <private/qqmlcompiler_p.h>
48 #include <private/qqmlcomponent_p.h>
49 #include <private/qqmlprofilerservice_p.h>
50
51 #include <QtCore/qdir.h>
52 #include <QtCore/qfile.h>
53 #include <QtCore/qdebug.h>
54 #include <QtCore/qmutex.h>
55 #include <QtCore/qthread.h>
56 #include <QtCore/qdiriterator.h>
57 #include <QtCore/qwaitcondition.h>
58 #include <QtQml/qqmlcomponent.h>
59 #include <QtQml/qqmlextensioninterface.h>
60
61 #if defined (Q_OS_UNIX)
62 #include <sys/types.h>
63 #include <dirent.h>
64 #endif
65
66 // #define DATABLOB_DEBUG
67
68 #ifdef DATABLOB_DEBUG
69
70 #define ASSERT_MAINTHREAD() do { if(m_thread->isThisThread()) qFatal("QQmlDataLoader: Caller not in main thread"); } while(false)
71 #define ASSERT_LOADTHREAD() do { if(!m_thread->isThisThread()) qFatal("QQmlDataLoader: Caller not in load thread"); } while(false)
72 #define ASSERT_CALLBACK() do { if(!m_manager || !m_manager->m_thread->isThisThread()) qFatal("QQmlDataBlob: An API call was made outside a callback"); } while(false)
73
74 #else
75
76 #define ASSERT_MAINTHREAD() 
77 #define ASSERT_LOADTHREAD()
78 #define ASSERT_CALLBACK()
79
80 #endif
81
82 QT_BEGIN_NAMESPACE
83
84 // This is a lame object that we need to ensure that slots connected to
85 // QNetworkReply get called in the correct thread (the loader thread).  
86 // As QQmlDataLoader lives in the main thread, and we can't use
87 // Qt::DirectConnection connections from a QNetworkReply (because then 
88 // sender() wont work), we need to insert this object in the middle.
89 class QQmlDataLoaderNetworkReplyProxy : public QObject
90 {
91     Q_OBJECT
92 public:
93     QQmlDataLoaderNetworkReplyProxy(QQmlDataLoader *l);
94
95 public slots:
96     void finished();
97     void downloadProgress(qint64, qint64);
98
99 private:
100     QQmlDataLoader *l;
101 };
102
103 class QQmlDataLoaderThread : public QQmlThread
104 {
105     typedef QQmlDataLoaderThread This;
106
107 public:
108     QQmlDataLoaderThread(QQmlDataLoader *loader);
109     QNetworkAccessManager *networkAccessManager() const;
110     QQmlDataLoaderNetworkReplyProxy *networkReplyProxy() const;
111
112     void load(QQmlDataBlob *b);
113     void loadAsync(QQmlDataBlob *b);
114     void loadWithStaticData(QQmlDataBlob *b, const QByteArray &);
115     void loadWithStaticDataAsync(QQmlDataBlob *b, const QByteArray &);
116     void callCompleted(QQmlDataBlob *b);
117     void callDownloadProgressChanged(QQmlDataBlob *b, qreal p);
118     void initializeEngine(QQmlExtensionInterface *, const char *);
119
120 protected:
121     virtual void shutdownThread();
122
123 private:
124     void loadThread(QQmlDataBlob *b);
125     void loadWithStaticDataThread(QQmlDataBlob *b, const QByteArray &);
126     void callCompletedMain(QQmlDataBlob *b);
127     void callDownloadProgressChangedMain(QQmlDataBlob *b, qreal p);
128     void initializeEngineMain(QQmlExtensionInterface *iface, const char *uri);
129
130     QQmlDataLoader *m_loader;
131     mutable QNetworkAccessManager *m_networkAccessManager;
132     mutable QQmlDataLoaderNetworkReplyProxy *m_networkReplyProxy;
133 };
134
135
136 QQmlDataLoaderNetworkReplyProxy::QQmlDataLoaderNetworkReplyProxy(QQmlDataLoader *l) 
137 : l(l) 
138 {
139 }
140
141 void QQmlDataLoaderNetworkReplyProxy::finished()
142 {
143     Q_ASSERT(sender());
144     Q_ASSERT(qobject_cast<QNetworkReply *>(sender()));
145     QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
146     l->networkReplyFinished(reply);
147 }
148
149 void QQmlDataLoaderNetworkReplyProxy::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
150 {
151     Q_ASSERT(sender());
152     Q_ASSERT(qobject_cast<QNetworkReply *>(sender()));
153     QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
154     l->networkReplyProgress(reply, bytesReceived, bytesTotal);
155 }
156
157 /*
158 Returns the set of QML files in path (qmldir, *.qml, *.js).  The caller
159 is responsible for deleting the returned data.
160 Returns 0 if the directory does not exist.
161 */
162 #if defined (Q_OS_UNIX) && !defined(Q_OS_DARWIN)
163 static QStringHash<bool> *qmlFilesInDirectory(const QString &path)
164 {
165     QByteArray name(QFile::encodeName(path));
166     DIR *dd = opendir(name);
167     if (!dd)
168         return 0;
169
170     struct dirent *result;
171     union {
172         struct dirent d;
173         char b[offsetof (struct dirent, d_name) + NAME_MAX + 1];
174     } u;
175
176     QStringHash<bool> *files = new QStringHash<bool>;
177     while (readdir_r(dd, &u.d, &result) == 0 && result != 0) {
178         if (!strcmp(u.d.d_name, "qmldir")) {
179             files->insert(QLatin1String("qmldir"), true);
180             continue;
181         }
182         int len = strlen(u.d.d_name);
183         if (len < 4)
184             continue;
185         if (!strcmp(u.d.d_name+len-4, ".qml") || !strcmp(u.d.d_name+len-3, ".js"))
186             files->insert(QFile::decodeName(u.d.d_name), true);
187 #if defined(Q_OS_DARWIN)
188         else if ((len > 6 && !strcmp(u.d.d_name+len-6, ".dylib")) || !strcmp(u.d.d_name+len-3, ".so")
189                   || (len > 7 && !strcmp(u.d.d_name+len-7, ".bundle")))
190             files->insert(QFile::decodeName(u.d.d_name), true);
191 #else  // Unix
192         else if (!strcmp(u.d.d_name+len-3, ".so") || !strcmp(u.d.d_name+len-3, ".sl"))
193             files->insert(QFile::decodeName(u.d.d_name), true);
194 #endif
195     }
196
197     closedir(dd);
198     return files;
199 }
200 #else
201 static QStringHash<bool> *qmlFilesInDirectory(const QString &path)
202 {
203     QDirIterator dir(path, QDir::Files);
204     if (!dir.hasNext())
205         return 0;
206     QStringHash<bool> *files = new QStringHash<bool>;
207     while (dir.hasNext()) {
208         dir.next();
209         QString fileName = dir.fileName();
210         if (fileName == QLatin1String("qmldir")
211                 || fileName.endsWith(QLatin1String(".qml"))
212                 || fileName.endsWith(QLatin1String(".js"))
213 #if defined(Q_OS_WIN32) || defined(Q_OS_WINCE)
214                 || fileName.endsWith(QLatin1String(".dll"))
215 #elif defined(Q_OS_DARWIN)
216                 || fileName.endsWith(QLatin1String(".dylib"))
217                 || fileName.endsWith(QLatin1String(".so"))
218                 || fileName.endsWith(QLatin1String(".bundle"))
219 #else  // Unix
220                 || fileName.endsWith(QLatin1String(".so"))
221                 || fileName.endsWith(QLatin1String(".sl"))
222 #endif
223                 ) {
224 #if defined(Q_OS_WIN32) || defined(Q_OS_WINCE) || defined(Q_OS_DARWIN)
225             fileName = fileName.toLower();
226 #endif
227             files->insert(fileName, true);
228         }
229     }
230     return files;
231 }
232 #endif
233
234
235 /*!
236 \class QQmlDataBlob
237 \brief The QQmlDataBlob encapsulates a data request that can be issued to a QQmlDataLoader.
238 \internal
239
240 QQmlDataBlob's are loaded by a QQmlDataLoader.  The user creates the QQmlDataBlob
241 and then calls QQmlDataLoader::load() or QQmlDataLoader::loadWithStaticData() to load it.
242 The QQmlDataLoader invokes callbacks on the QQmlDataBlob as data becomes available.
243 */
244
245 /*!
246 \enum QQmlDataBlob::Status
247
248 This enum describes the status of the data blob.
249
250 \list
251 \o Null The blob has not yet been loaded by a QQmlDataLoader
252 \o Loading The blob is loading network data.  The QQmlDataBlob::setData() callback has not yet been
253 invoked or has not yet returned.
254 \o WaitingForDependencies The blob is waiting for dependencies to be done before continueing.  This status
255 only occurs after the QQmlDataBlob::setData() callback has been made, and when the blob has outstanding
256 dependencies.
257 \o Complete The blob's data has been loaded and all dependencies are done.
258 \o Error An error has been set on this blob.
259 \endlist
260 */
261
262 /*!
263 \enum QQmlDataBlob::Type
264
265 This enum describes the type of the data blob.
266
267 \list
268 \o QmlFile This is a QQmlTypeData
269 \o JavaScriptFile This is a QQmlScriptData
270 \o QmldirFile This is a QQmlQmldirData
271 \endlist
272 */
273
274 /*!
275 Create a new QQmlDataBlob for \a url and of the provided \a type.
276 */
277 QQmlDataBlob::QQmlDataBlob(const QUrl &url, Type type)
278 : m_type(type), m_url(url), m_finalUrl(url), m_manager(0), m_redirectCount(0), 
279   m_inCallback(false), m_isDone(false)
280 {
281 }
282
283 /*!  \internal */
284 QQmlDataBlob::~QQmlDataBlob()
285 {
286     Q_ASSERT(m_waitingOnMe.isEmpty());
287
288     cancelAllWaitingFor();
289 }
290
291 /*!
292 Returns the type provided to the constructor.
293 */
294 QQmlDataBlob::Type QQmlDataBlob::type() const
295 {
296     return m_type;
297 }
298
299 /*!
300 Returns the blob's status.
301 */
302 QQmlDataBlob::Status QQmlDataBlob::status() const
303 {
304     return m_data.status();
305 }
306
307 /*!
308 Returns true if the status is Null.
309 */
310 bool QQmlDataBlob::isNull() const
311 {
312     return status() == Null;
313 }
314
315 /*!
316 Returns true if the status is Loading.
317 */
318 bool QQmlDataBlob::isLoading() const
319 {
320     return status() == Loading;
321 }
322
323 /*!
324 Returns true if the status is WaitingForDependencies.
325 */
326 bool QQmlDataBlob::isWaiting() const
327 {
328     return status() == WaitingForDependencies;
329 }
330
331 /*!
332 Returns true if the status is Complete.
333 */
334 bool QQmlDataBlob::isComplete() const
335 {
336     return status() == Complete;
337 }
338
339 /*!
340 Returns true if the status is Error.
341 */
342 bool QQmlDataBlob::isError() const
343 {
344     return status() == Error;
345 }
346
347 /*!
348 Returns true if the status is Complete or Error.
349 */
350 bool QQmlDataBlob::isCompleteOrError() const
351 {
352     Status s = status();
353     return s == Error || s == Complete;
354 }
355
356 /*!
357 Returns the data download progress from 0 to 1.
358 */
359 qreal QQmlDataBlob::progress() const
360 {
361     quint8 p = m_data.progress();
362     if (p == 0xFF) return 1.;
363     else return qreal(p) / qreal(0xFF);
364 }
365
366 /*!
367 Returns the blob url passed to the constructor.  If a network redirect
368 happens while fetching the data, this url remains the same.
369
370 \sa finalUrl()
371 */
372 QUrl QQmlDataBlob::url() const
373 {
374     return m_url;
375 }
376
377 /*!
378 Returns the final url of the data.  Initially this is the same as
379 url(), but if a network redirect happens while fetching the data, this url
380 is updated to reflect the new location.
381
382 May only be called from the load thread, or after the blob isCompleteOrError().
383 */
384 QUrl QQmlDataBlob::finalUrl() const
385 {
386     Q_ASSERT(isCompleteOrError() || (m_manager && m_manager->m_thread->isThisThread()));
387     return m_finalUrl;
388 }
389
390 /*!
391 Returns the finalUrl() as a string.
392 */
393 QString QQmlDataBlob::finalUrlString() const
394 {
395     Q_ASSERT(isCompleteOrError() || (m_manager && m_manager->m_thread->isThisThread()));
396     if (m_finalUrlString.isEmpty())
397         m_finalUrlString = m_finalUrl.toString();
398
399     return m_finalUrlString;
400 }
401
402 /*!
403 Return the errors on this blob.
404
405 May only be called from the load thread, or after the blob isCompleteOrError().
406 */
407 QList<QQmlError> QQmlDataBlob::errors() const
408 {
409     Q_ASSERT(isCompleteOrError() || (m_manager && m_manager->m_thread->isThisThread()));
410     return m_errors;
411 }
412
413 /*!
414 Mark this blob as having \a errors.
415
416 All outstanding dependencies will be cancelled.  Requests to add new dependencies 
417 will be ignored.  Entry into the Error state is irreversable.
418
419 The setError() method may only be called from within a QQmlDataBlob callback.
420 */
421 void QQmlDataBlob::setError(const QQmlError &errors)
422 {
423     ASSERT_CALLBACK();
424
425     QList<QQmlError> l;
426     l << errors;
427     setError(l);
428 }
429
430 /*!
431 \overload
432 */
433 void QQmlDataBlob::setError(const QList<QQmlError> &errors)
434 {
435     ASSERT_CALLBACK();
436
437     Q_ASSERT(status() != Error);
438     Q_ASSERT(m_errors.isEmpty());
439
440     m_errors = errors; // Must be set before the m_data fence
441     m_data.setStatus(Error);
442
443     cancelAllWaitingFor();
444
445     if (!m_inCallback)
446         tryDone();
447 }
448
449 /*! 
450 Wait for \a blob to become complete or to error.  If \a blob is already 
451 complete or in error, or this blob is already complete, this has no effect.
452
453 The setError() method may only be called from within a QQmlDataBlob callback.
454 */
455 void QQmlDataBlob::addDependency(QQmlDataBlob *blob)
456 {
457     ASSERT_CALLBACK();
458
459     Q_ASSERT(status() != Null);
460
461     if (!blob ||
462         blob->status() == Error || blob->status() == Complete ||
463         status() == Error || status() == Complete || m_isDone || 
464         m_waitingFor.contains(blob))
465         return;
466
467     blob->addref();
468
469     m_data.setStatus(WaitingForDependencies);
470
471     m_waitingFor.append(blob);
472     blob->m_waitingOnMe.append(this);
473 }
474
475 /*!
476 \fn void QQmlDataBlob::dataReceived(const QByteArray &data)
477
478 Invoked when data for the blob is received.  Implementors should use this callback
479 to determine a blob's dependencies.  Within this callback you may call setError()
480 or addDependency().
481 */
482
483 /*!
484 Invoked once data has either been received or a network error occurred, and all 
485 dependencies are complete.
486
487 You can set an error in this method, but you cannot add new dependencies.  Implementors
488 should use this callback to finalize processing of data.
489
490 The default implementation does nothing.
491
492 XXX Rename processData() or some such to avoid confusion between done() (processing thread)
493 and completed() (main thread)
494 */
495 void QQmlDataBlob::done()
496 {
497 }
498
499 /*!
500 Invoked if there is a network error while fetching this blob.
501
502 The default implementation sets an appropriate QQmlError.
503 */
504 void QQmlDataBlob::networkError(QNetworkReply::NetworkError networkError)
505 {
506     Q_UNUSED(networkError);
507
508     QQmlError error;
509     error.setUrl(m_finalUrl);
510
511     const char *errorString = 0;
512     switch (networkError) {
513         default:
514             errorString = "Network error";
515             break;
516         case QNetworkReply::ConnectionRefusedError:
517             errorString = "Connection refused";
518             break;
519         case QNetworkReply::RemoteHostClosedError:
520             errorString = "Remote host closed the connection";
521             break;
522         case QNetworkReply::HostNotFoundError:
523             errorString = "Host not found";
524             break;
525         case QNetworkReply::TimeoutError:
526             errorString = "Timeout";
527             break;
528         case QNetworkReply::ProxyConnectionRefusedError:
529         case QNetworkReply::ProxyConnectionClosedError:
530         case QNetworkReply::ProxyNotFoundError:
531         case QNetworkReply::ProxyTimeoutError:
532         case QNetworkReply::ProxyAuthenticationRequiredError:
533         case QNetworkReply::UnknownProxyError:
534             errorString = "Proxy error";
535             break;
536         case QNetworkReply::ContentAccessDenied:
537             errorString = "Access denied";
538             break;
539         case QNetworkReply::ContentNotFoundError:
540             errorString = "File not found";
541             break;
542         case QNetworkReply::AuthenticationRequiredError:
543             errorString = "Authentication required";
544             break;
545     };
546
547     error.setDescription(QLatin1String(errorString));
548
549     setError(error);
550 }
551
552 /*! 
553 Called if \a blob, which was previously waited for, has an error.
554
555 The default implementation does nothing.
556 */
557 void QQmlDataBlob::dependencyError(QQmlDataBlob *blob)
558 {
559     Q_UNUSED(blob);
560 }
561
562 /*!
563 Called if \a blob, which was previously waited for, has completed.
564
565 The default implementation does nothing.
566 */
567 void QQmlDataBlob::dependencyComplete(QQmlDataBlob *blob)
568 {
569     Q_UNUSED(blob);
570 }
571
572 /*! 
573 Called when all blobs waited for have completed.  This occurs regardless of 
574 whether they are in error, or complete state.  
575
576 The default implementation does nothing.
577 */
578 void QQmlDataBlob::allDependenciesDone()
579 {
580 }
581
582 /*!
583 Called when the download progress of this blob changes.  \a progress goes
584 from 0 to 1.
585
586 This callback is only invoked if an asynchronous load for this blob is 
587 made.  An asynchronous load is one in which the Asynchronous mode is
588 specified explicitly, or one that is implicitly delayed due to a network 
589 operation.
590
591 The default implementation does nothing.
592 */
593 void QQmlDataBlob::downloadProgressChanged(qreal progress)
594 {
595     Q_UNUSED(progress);
596 }
597
598 /*!
599 Invoked on the main thread sometime after done() was called on the load thread.
600
601 You cannot modify the blobs state at all in this callback and cannot depend on the
602 order or timeliness of these callbacks.  Implementors should use this callback to notify 
603 dependencies on the main thread that the blob is done and not a lot else.
604
605 This callback is only invoked if an asynchronous load for this blob is 
606 made.  An asynchronous load is one in which the Asynchronous mode is
607 specified explicitly, or one that is implicitly delayed due to a network 
608 operation.
609
610 The default implementation does nothing.
611 */
612 void QQmlDataBlob::completed()
613 {
614 }
615
616
617 void QQmlDataBlob::tryDone()
618 {
619     if (status() != Loading && m_waitingFor.isEmpty() && !m_isDone) {
620         m_isDone = true;
621         addref();
622
623 #ifdef DATABLOB_DEBUG
624         qWarning("QQmlDataBlob::done() %s", qPrintable(url().toString()));
625 #endif
626         done();
627
628         if (status() != Error)
629             m_data.setStatus(Complete);
630
631         notifyAllWaitingOnMe();
632
633         // Locking is not required here, as anyone expecting callbacks must
634         // already be protected against the blob being completed (as set above);
635         if (m_data.isAsync()) {
636 #ifdef DATABLOB_DEBUG
637             qWarning("QQmlDataBlob: Dispatching completed");
638 #endif
639             m_manager->m_thread->callCompleted(this);
640         }
641
642         release();
643     }
644 }
645
646 void QQmlDataBlob::cancelAllWaitingFor()
647 {
648     while (m_waitingFor.count()) {
649         QQmlDataBlob *blob = m_waitingFor.takeLast();
650
651         Q_ASSERT(blob->m_waitingOnMe.contains(this));
652
653         blob->m_waitingOnMe.removeOne(this);
654
655         blob->release();
656     }
657 }
658
659 void QQmlDataBlob::notifyAllWaitingOnMe()
660 {
661     while (m_waitingOnMe.count()) {
662         QQmlDataBlob *blob = m_waitingOnMe.takeLast();
663
664         Q_ASSERT(blob->m_waitingFor.contains(this));
665
666         blob->notifyComplete(this);
667     }
668 }
669
670 void QQmlDataBlob::notifyComplete(QQmlDataBlob *blob)
671 {
672     Q_ASSERT(m_waitingFor.contains(blob));
673     Q_ASSERT(blob->status() == Error || blob->status() == Complete);
674
675     m_inCallback = true;
676
677     if (blob->status() == Error) {
678         dependencyError(blob);
679     } else if (blob->status() == Complete) {
680         dependencyComplete(blob);
681     }
682
683     m_waitingFor.removeOne(blob);
684     blob->release();
685
686     if (!isError() && m_waitingFor.isEmpty()) 
687         allDependenciesDone();
688
689     m_inCallback = false;
690
691     tryDone();
692 }
693
694 #define TD_STATUS_MASK 0x0000FFFF
695 #define TD_STATUS_SHIFT 0
696 #define TD_PROGRESS_MASK 0x00FF0000
697 #define TD_PROGRESS_SHIFT 16
698 #define TD_ASYNC_MASK 0x80000000
699
700 QQmlDataBlob::ThreadData::ThreadData()
701 : _p(0)
702 {
703 }
704
705 QQmlDataBlob::Status QQmlDataBlob::ThreadData::status() const
706 {
707     return QQmlDataBlob::Status((_p.load() & TD_STATUS_MASK) >> TD_STATUS_SHIFT);
708 }
709
710 void QQmlDataBlob::ThreadData::setStatus(QQmlDataBlob::Status status)
711 {
712     while (true) {
713         int d = _p.load();
714         int nd = (d & ~TD_STATUS_MASK) | ((status << TD_STATUS_SHIFT) & TD_STATUS_MASK);
715         if (d == nd || _p.testAndSetOrdered(d, nd)) return;
716     }
717 }
718
719 bool QQmlDataBlob::ThreadData::isAsync() const
720 {
721     return _p.load() & TD_ASYNC_MASK;
722 }
723
724 void QQmlDataBlob::ThreadData::setIsAsync(bool v)
725 {
726     while (true) {
727         int d = _p.load();
728         int nd = (d & ~TD_ASYNC_MASK) | (v?TD_ASYNC_MASK:0);
729         if (d == nd || _p.testAndSetOrdered(d, nd)) return;
730     }
731 }
732
733 quint8 QQmlDataBlob::ThreadData::progress() const
734 {
735     return quint8((_p.load() & TD_PROGRESS_MASK) >> TD_PROGRESS_SHIFT);
736 }
737
738 void QQmlDataBlob::ThreadData::setProgress(quint8 v)
739 {
740     while (true) {
741         int d = _p.load();
742         int nd = (d & ~TD_PROGRESS_MASK) | ((v << TD_PROGRESS_SHIFT) & TD_PROGRESS_MASK);
743         if (d == nd || _p.testAndSetOrdered(d, nd)) return;
744     }
745 }
746
747 QQmlDataLoaderThread::QQmlDataLoaderThread(QQmlDataLoader *loader)
748 : m_loader(loader), m_networkAccessManager(0), m_networkReplyProxy(0)
749 {
750 }
751
752 QNetworkAccessManager *QQmlDataLoaderThread::networkAccessManager() const
753 {
754     Q_ASSERT(isThisThread());
755     if (!m_networkAccessManager) {
756         m_networkAccessManager = QQmlEnginePrivate::get(m_loader->engine())->createNetworkAccessManager(0);
757         m_networkReplyProxy = new QQmlDataLoaderNetworkReplyProxy(m_loader);
758     }
759
760     return m_networkAccessManager;
761 }
762
763 QQmlDataLoaderNetworkReplyProxy *QQmlDataLoaderThread::networkReplyProxy() const
764 {
765     Q_ASSERT(isThisThread());
766     Q_ASSERT(m_networkReplyProxy); // Must call networkAccessManager() first
767     return m_networkReplyProxy;
768 }
769
770 void QQmlDataLoaderThread::load(QQmlDataBlob *b) 
771
772     b->addref();
773     callMethodInThread(&This::loadThread, b); 
774 }
775
776 void QQmlDataLoaderThread::loadAsync(QQmlDataBlob *b)
777 {
778     b->addref();
779     postMethodToThread(&This::loadThread, b);
780 }
781
782 void QQmlDataLoaderThread::loadWithStaticData(QQmlDataBlob *b, const QByteArray &d)
783 {
784     b->addref();
785     callMethodInThread(&This::loadWithStaticDataThread, b, d);
786 }
787
788 void QQmlDataLoaderThread::loadWithStaticDataAsync(QQmlDataBlob *b, const QByteArray &d)
789 {
790     b->addref();
791     postMethodToThread(&This::loadWithStaticDataThread, b, d);
792 }
793
794 void QQmlDataLoaderThread::callCompleted(QQmlDataBlob *b) 
795
796     b->addref(); 
797     postMethodToMain(&This::callCompletedMain, b); 
798 }
799
800 void QQmlDataLoaderThread::callDownloadProgressChanged(QQmlDataBlob *b, qreal p) 
801
802     b->addref(); 
803     postMethodToMain(&This::callDownloadProgressChangedMain, b, p); 
804 }
805
806 void QQmlDataLoaderThread::initializeEngine(QQmlExtensionInterface *iface, 
807                                                     const char *uri)
808 {
809     callMethodInMain(&This::initializeEngineMain, iface, uri);
810 }
811
812 void QQmlDataLoaderThread::shutdownThread()
813 {
814     delete m_networkAccessManager;
815     m_networkAccessManager = 0;
816     delete m_networkReplyProxy;
817     m_networkReplyProxy = 0;
818 }
819
820 void QQmlDataLoaderThread::loadThread(QQmlDataBlob *b) 
821
822     m_loader->loadThread(b); 
823     b->release();
824 }
825
826 void QQmlDataLoaderThread::loadWithStaticDataThread(QQmlDataBlob *b, const QByteArray &d)
827 {
828     m_loader->loadWithStaticDataThread(b, d);
829     b->release();
830 }
831
832 void QQmlDataLoaderThread::callCompletedMain(QQmlDataBlob *b) 
833
834 #ifdef DATABLOB_DEBUG
835     qWarning("QQmlDataLoaderThread: %s completed() callback", qPrintable(b->url().toString())); 
836 #endif
837     b->completed(); 
838     b->release(); 
839 }
840
841 void QQmlDataLoaderThread::callDownloadProgressChangedMain(QQmlDataBlob *b, qreal p) 
842
843 #ifdef DATABLOB_DEBUG
844     qWarning("QQmlDataLoaderThread: %s downloadProgressChanged(%f) callback", 
845              qPrintable(b->url().toString()), p); 
846 #endif
847     b->downloadProgressChanged(p); 
848     b->release(); 
849 }
850
851 void QQmlDataLoaderThread::initializeEngineMain(QQmlExtensionInterface *iface, 
852                                                         const char *uri)
853 {
854     Q_ASSERT(m_loader->engine()->thread() == QThread::currentThread());
855     iface->initializeEngine(m_loader->engine(), uri);
856 }
857
858 /*!
859 \class QQmlDataLoader
860 \brief The QQmlDataLoader class abstracts loading files and their dependencies over the network.
861 \internal
862
863 The QQmlDataLoader class is provided for the exclusive use of the QQmlTypeLoader class.
864
865 Clients create QQmlDataBlob instances and submit them to the QQmlDataLoader class
866 through the QQmlDataLoader::load() or QQmlDataLoader::loadWithStaticData() methods.
867 The loader then fetches the data over the network or from the local file system in an efficient way.
868 QQmlDataBlob is an abstract class, so should always be specialized.
869
870 Once data is received, the QQmlDataBlob::dataReceived() method is invoked on the blob.  The
871 derived class should use this callback to process the received data.  Processing of the data can 
872 result in an error being set (QQmlDataBlob::setError()), or one or more dependencies being
873 created (QQmlDataBlob::addDependency()).  Dependencies are other QQmlDataBlob's that
874 are required before processing can fully complete.
875
876 To complete processing, the QQmlDataBlob::done() callback is invoked.  done() is called when
877 one of these three preconditions are met.
878
879 \list 1
880 \o The QQmlDataBlob has no dependencies.
881 \o The QQmlDataBlob has an error set.
882 \o All the QQmlDataBlob's dependencies are themselves "done()".
883 \endlist
884
885 Thus QQmlDataBlob::done() will always eventually be called, even if the blob has an error set.
886 */
887
888 /*!
889 Create a new QQmlDataLoader for \a engine.
890 */
891 QQmlDataLoader::QQmlDataLoader(QQmlEngine *engine)
892 : m_engine(engine), m_thread(new QQmlDataLoaderThread(this))
893 {
894 }
895
896 /*! \internal */
897 QQmlDataLoader::~QQmlDataLoader()
898 {
899     for (NetworkReplies::Iterator iter = m_networkReplies.begin(); iter != m_networkReplies.end(); ++iter) 
900         (*iter)->release();
901
902     m_thread->shutdown();
903     delete m_thread;
904 }
905
906 void QQmlDataLoader::lock()
907 {
908     m_thread->lock();
909 }
910
911 void QQmlDataLoader::unlock()
912 {
913     m_thread->unlock();
914 }
915
916 /*!
917 Load the provided \a blob from the network or filesystem.
918
919 The loader must be locked.
920 */
921 void QQmlDataLoader::load(QQmlDataBlob *blob, Mode mode)
922 {
923 #ifdef DATABLOB_DEBUG
924     qWarning("QQmlDataLoader::load(%s): %s thread", qPrintable(blob->m_url.toString()), 
925              m_thread->isThisThread()?"Compile":"Engine");
926 #endif
927
928     Q_ASSERT(blob->status() == QQmlDataBlob::Null);
929     Q_ASSERT(blob->m_manager == 0);
930
931     blob->m_data.setStatus(QQmlDataBlob::Loading);
932     blob->m_manager = this;
933
934     if (m_thread->isThisThread()) {
935         unlock();
936         loadThread(blob);
937         lock();
938     } else if (mode == PreferSynchronous) {
939         unlock();
940         m_thread->load(blob);
941         lock();
942         if (!blob->isCompleteOrError())
943             blob->m_data.setIsAsync(true);
944     } else {
945         Q_ASSERT(mode == Asynchronous);
946         blob->m_data.setIsAsync(true);
947         unlock();
948         m_thread->loadAsync(blob);
949         lock();
950     }
951 }
952
953 /*!
954 Load the provided \a blob with \a data.  The blob's URL is not used by the data loader in this case.
955
956 The loader must be locked.
957 */
958 void QQmlDataLoader::loadWithStaticData(QQmlDataBlob *blob, const QByteArray &data, Mode mode)
959 {
960 #ifdef DATABLOB_DEBUG
961     qWarning("QQmlDataLoader::loadWithStaticData(%s, data): %s thread", qPrintable(blob->m_url.toString()), 
962              m_thread->isThisThread()?"Compile":"Engine");
963 #endif
964
965     Q_ASSERT(blob->status() == QQmlDataBlob::Null);
966     Q_ASSERT(blob->m_manager == 0);
967     
968     blob->m_data.setStatus(QQmlDataBlob::Loading);
969     blob->m_manager = this;
970
971     if (m_thread->isThisThread()) {
972         unlock();
973         loadWithStaticDataThread(blob, data);
974         lock();
975     } else if (mode == PreferSynchronous) {
976         unlock();
977         m_thread->loadWithStaticData(blob, data);
978         lock();
979         if (!blob->isCompleteOrError())
980             blob->m_data.setIsAsync(true);
981     } else {
982         Q_ASSERT(mode == Asynchronous);
983         blob->m_data.setIsAsync(true);
984         unlock();
985         m_thread->loadWithStaticDataAsync(blob, data);
986         lock();
987     }
988 }
989
990 void QQmlDataLoader::loadWithStaticDataThread(QQmlDataBlob *blob, const QByteArray &data)
991 {
992     ASSERT_LOADTHREAD();
993
994     setData(blob, data);
995 }
996
997 void QQmlDataLoader::loadThread(QQmlDataBlob *blob)
998 {
999     ASSERT_LOADTHREAD();
1000
1001     if (blob->m_url.isEmpty()) {
1002         QQmlError error;
1003         error.setDescription(QLatin1String("Invalid null URL"));
1004         blob->setError(error);
1005         return;
1006     }
1007
1008     QString lf = QQmlEnginePrivate::urlToLocalFileOrQrc(blob->m_url);
1009
1010     if (!lf.isEmpty()) {
1011         if (!QQml_isFileCaseCorrect(lf)) {
1012             QQmlError error;
1013             error.setUrl(blob->m_url);
1014             error.setDescription(QLatin1String("File name case mismatch"));
1015             blob->setError(error);
1016             return;
1017         }
1018         QFile file(lf);
1019         if (file.open(QFile::ReadOnly)) {
1020             QByteArray data = file.readAll();
1021
1022             blob->m_data.setProgress(0xFF);
1023             if (blob->m_data.isAsync())
1024                 m_thread->callDownloadProgressChanged(blob, 1.);
1025
1026             setData(blob, data);
1027         } else {
1028             blob->networkError(QNetworkReply::ContentNotFoundError);
1029         }
1030
1031     } else {
1032
1033         QNetworkReply *reply = m_thread->networkAccessManager()->get(QNetworkRequest(blob->m_url));
1034         QObject *nrp = m_thread->networkReplyProxy();
1035         QObject::connect(reply, SIGNAL(downloadProgress(qint64,qint64)), 
1036                          nrp, SLOT(downloadProgress(qint64,qint64)));
1037         QObject::connect(reply, SIGNAL(finished()), 
1038                          nrp, SLOT(finished()));
1039         m_networkReplies.insert(reply, blob);
1040
1041         blob->addref();
1042     }
1043 }
1044
1045 #define DATALOADER_MAXIMUM_REDIRECT_RECURSION 16
1046
1047 void QQmlDataLoader::networkReplyFinished(QNetworkReply *reply)
1048 {
1049     Q_ASSERT(m_thread->isThisThread());
1050
1051     reply->deleteLater();
1052
1053     QQmlDataBlob *blob = m_networkReplies.take(reply);
1054
1055     Q_ASSERT(blob);
1056
1057     blob->m_redirectCount++;
1058
1059     if (blob->m_redirectCount < DATALOADER_MAXIMUM_REDIRECT_RECURSION) {
1060         QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
1061         if (redirect.isValid()) {
1062             QUrl url = reply->url().resolved(redirect.toUrl());
1063             blob->m_finalUrl = url;
1064
1065             QNetworkReply *reply = m_thread->networkAccessManager()->get(QNetworkRequest(url));
1066             QObject *nrp = m_thread->networkReplyProxy();
1067             QObject::connect(reply, SIGNAL(finished()), nrp, SLOT(finished()));
1068             m_networkReplies.insert(reply, blob);
1069             return;
1070         }
1071     }
1072
1073     if (reply->error()) {
1074         blob->networkError(reply->error());
1075     } else {
1076         QByteArray data = reply->readAll();
1077         setData(blob, data);
1078     }
1079
1080     blob->release();
1081 }
1082
1083 void QQmlDataLoader::networkReplyProgress(QNetworkReply *reply,
1084                                                   qint64 bytesReceived, qint64 bytesTotal)
1085 {
1086     Q_ASSERT(m_thread->isThisThread());
1087
1088     QQmlDataBlob *blob = m_networkReplies.value(reply);
1089
1090     Q_ASSERT(blob);
1091
1092     if (bytesTotal != 0) {
1093         quint8 progress = 0xFF * (qreal(bytesReceived) / qreal(bytesTotal));
1094         blob->m_data.setProgress(progress);
1095         if (blob->m_data.isAsync())
1096             m_thread->callDownloadProgressChanged(blob, blob->m_data.progress());
1097     }
1098 }
1099
1100 /*!
1101 Return the QQmlEngine associated with this loader
1102 */
1103 QQmlEngine *QQmlDataLoader::engine() const
1104 {
1105     return m_engine;
1106 }
1107
1108 /*!
1109 Call the initializeEngine() method on \a iface.  Used by QQmlImportDatabase to ensure it
1110 gets called in the correct thread.
1111 */
1112 void QQmlDataLoader::initializeEngine(QQmlExtensionInterface *iface, 
1113                                               const char *uri)
1114 {
1115     Q_ASSERT(m_thread->isThisThread() || engine()->thread() == QThread::currentThread());
1116
1117     if (m_thread->isThisThread()) {
1118         m_thread->initializeEngine(iface, uri);
1119     } else {
1120         Q_ASSERT(engine()->thread() == QThread::currentThread());
1121         iface->initializeEngine(engine(), uri);
1122     }
1123 }
1124
1125
1126 void QQmlDataLoader::setData(QQmlDataBlob *blob, const QByteArray &data)
1127 {
1128     blob->m_inCallback = true;
1129
1130     blob->dataReceived(data);
1131
1132     if (!blob->isError() && !blob->isWaiting())
1133         blob->allDependenciesDone();
1134
1135     if (blob->status() != QQmlDataBlob::Error) 
1136         blob->m_data.setStatus(QQmlDataBlob::WaitingForDependencies);
1137
1138     blob->m_inCallback = false;
1139
1140     blob->tryDone();
1141 }
1142
1143 /*!
1144 Constructs a new type loader that uses the given \a engine.
1145 */
1146 QQmlTypeLoader::QQmlTypeLoader(QQmlEngine *engine)
1147 : QQmlDataLoader(engine)
1148 {
1149 }
1150
1151 /*!
1152 Destroys the type loader, first clearing the cache of any information about
1153 loaded files.
1154 */
1155 QQmlTypeLoader::~QQmlTypeLoader()
1156 {
1157     clearCache();
1158 }
1159
1160 /*!
1161 \enum QQmlTypeLoader::Option
1162
1163 This enum defines the options that control the way type data is handled.
1164
1165 \value None             The default value, indicating that no other options
1166                         are enabled.
1167 \value PreserveParser   The parser used to handle the type data is preserved
1168                         after the data has been parsed.
1169 */
1170
1171 /*!
1172 Returns a QQmlTypeData for the specified \a url.  The QQmlTypeData may be cached.
1173 */
1174 QQmlTypeData *QQmlTypeLoader::get(const QUrl &url)
1175 {
1176     Q_ASSERT(!url.isRelative() && 
1177             (QQmlEnginePrivate::urlToLocalFileOrQrc(url).isEmpty() || 
1178              !QDir::isRelativePath(QQmlEnginePrivate::urlToLocalFileOrQrc(url))));
1179
1180     lock();
1181     
1182     QQmlTypeData *typeData = m_typeCache.value(url);
1183
1184     if (!typeData) {
1185         typeData = new QQmlTypeData(url, None, this);
1186         m_typeCache.insert(url, typeData);
1187         QQmlDataLoader::load(typeData);
1188     }
1189
1190     typeData->addref();
1191
1192     unlock();
1193
1194     return typeData;
1195 }
1196
1197 /*!
1198 Returns a QQmlTypeData for the given \a data with the provided base \a url.  The 
1199 QQmlTypeData will not be cached.
1200
1201 The specified \a options control how the loader handles type data.
1202 */
1203 QQmlTypeData *QQmlTypeLoader::get(const QByteArray &data, const QUrl &url, Options options)
1204 {
1205     lock();
1206
1207     QQmlTypeData *typeData = new QQmlTypeData(url, options, this);
1208     QQmlDataLoader::loadWithStaticData(typeData, data);
1209
1210     unlock();
1211
1212     return typeData;
1213 }
1214
1215 /*!
1216 Return a QQmlScriptBlob for \a url.  The QQmlScriptData may be cached.
1217 */
1218 QQmlScriptBlob *QQmlTypeLoader::getScript(const QUrl &url)
1219 {
1220     Q_ASSERT(!url.isRelative() && 
1221             (QQmlEnginePrivate::urlToLocalFileOrQrc(url).isEmpty() || 
1222              !QDir::isRelativePath(QQmlEnginePrivate::urlToLocalFileOrQrc(url))));
1223
1224     lock();
1225
1226     QQmlScriptBlob *scriptBlob = m_scriptCache.value(url);
1227
1228     if (!scriptBlob) {
1229         scriptBlob = new QQmlScriptBlob(url, this);
1230         m_scriptCache.insert(url, scriptBlob);
1231         QQmlDataLoader::load(scriptBlob);
1232     }
1233
1234     scriptBlob->addref();
1235
1236     unlock();
1237
1238     return scriptBlob;
1239 }
1240
1241 /*!
1242 Returns a QQmlQmldirData for \a url.  The QQmlQmldirData may be cached.
1243 */
1244 QQmlQmldirData *QQmlTypeLoader::getQmldir(const QUrl &url)
1245 {
1246     Q_ASSERT(!url.isRelative() && 
1247             (QQmlEnginePrivate::urlToLocalFileOrQrc(url).isEmpty() || 
1248              !QDir::isRelativePath(QQmlEnginePrivate::urlToLocalFileOrQrc(url))));
1249
1250     lock();
1251
1252     QQmlQmldirData *qmldirData = m_qmldirCache.value(url);
1253
1254     if (!qmldirData) {
1255         qmldirData = new QQmlQmldirData(url);
1256         m_qmldirCache.insert(url, qmldirData);
1257         QQmlDataLoader::load(qmldirData);
1258     }
1259
1260     qmldirData->addref();
1261
1262     unlock();
1263
1264     return qmldirData;
1265 }
1266
1267 /*!
1268 Returns the absolute filename of path via a directory cache for files named
1269 "qmldir", "*.qml", "*.js", and plugins.
1270 Returns a empty string if the path does not exist.
1271 */
1272 QString QQmlTypeLoader::absoluteFilePath(const QString &path)
1273 {
1274     if (path.isEmpty())
1275         return QString();
1276     if (path.at(0) == QLatin1Char(':')) {
1277         // qrc resource
1278         QFileInfo fileInfo(path);
1279         return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString();
1280     }
1281 #if defined(Q_OS_WIN32) || defined(Q_OS_WINCE) || defined(Q_OS_DARWIN)
1282     QString lowPath = path.toLower();
1283     int lastSlash = lowPath.lastIndexOf(QLatin1Char('/'));
1284     QString dirPath = lowPath.left(lastSlash);
1285 #else
1286     int lastSlash = path.lastIndexOf(QLatin1Char('/'));
1287     QStringRef dirPath(&path, 0, lastSlash);
1288 #endif
1289
1290     StringSet **fileSet = m_importDirCache.value(QHashedStringRef(dirPath.constData(), dirPath.length()));
1291     if (!fileSet) {
1292 #if defined(Q_OS_WIN32) || defined(Q_OS_WINCE) || defined(Q_OS_DARWIN)
1293         QHashedString dirPathString(dirPath);
1294 #else
1295         QHashedString dirPathString(dirPath.toString());
1296 #endif
1297         StringSet *files = qmlFilesInDirectory(dirPathString);
1298         m_importDirCache.insert(dirPathString, files);
1299         fileSet = m_importDirCache.value(dirPathString);
1300     }
1301     if (!(*fileSet))
1302         return QString();
1303
1304 #if defined(Q_OS_WIN32) || defined(Q_OS_WINCE) || defined(Q_OS_DARWIN)
1305     QString absoluteFilePath = (*fileSet)->contains(QHashedStringRef(lowPath.constData()+lastSlash+1, lowPath.length()-lastSlash-1)) ? path : QString();
1306 #else
1307     QString absoluteFilePath = (*fileSet)->contains(QHashedStringRef(path.constData()+lastSlash+1, path.length()-lastSlash-1)) ? path : QString();
1308 #endif
1309     if (absoluteFilePath.length() > 2 && absoluteFilePath.at(0) != QLatin1Char('/') && absoluteFilePath.at(1) != QLatin1Char(':'))
1310         absoluteFilePath = QFileInfo(absoluteFilePath).absoluteFilePath();
1311
1312     return absoluteFilePath;
1313 }
1314
1315 /*!
1316 Returns true if the path is a directory via a directory cache.  Cache is
1317 shared with absoluteFilePath().
1318 */
1319 bool QQmlTypeLoader::directoryExists(const QString &path)
1320 {
1321     if (path.isEmpty())
1322         return false;
1323     if (path.at(0) == QLatin1Char(':')) {
1324         // qrc resource
1325         QFileInfo fileInfo(path);
1326         return fileInfo.exists() && fileInfo.isDir();
1327     }
1328
1329     int length = path.length();
1330     if (path.endsWith(QLatin1Char('/')))
1331         --length;
1332 #if defined(Q_OS_WIN32) || defined(Q_OS_WINCE) || defined(Q_OS_DARWIN)
1333     QString dirPath = path.left(length).toLower();
1334 #else
1335     QStringRef dirPath(&path, 0, length);
1336 #endif
1337
1338     StringSet **fileSet = m_importDirCache.value(QHashedStringRef(dirPath.constData(), dirPath.length()));
1339     if (!fileSet) {
1340 #if defined(Q_OS_WIN32) || defined(Q_OS_WINCE) || defined(Q_OS_DARWIN)
1341         QHashedString dirPathString(dirPath);
1342 #else
1343         QHashedString dirPathString(dirPath.toString());
1344 #endif
1345         StringSet *files = qmlFilesInDirectory(dirPathString);
1346         m_importDirCache.insert(dirPathString, files);
1347         fileSet = m_importDirCache.value(dirPathString);
1348     }
1349
1350     return (*fileSet);
1351 }
1352
1353
1354 /*!
1355 Return a QQmlDirParser for absoluteFilePath.  The QQmlDirParser may be cached.
1356 */
1357 const QQmlDirParser *QQmlTypeLoader::qmlDirParser(const QString &absoluteFilePath)
1358 {
1359     QQmlDirParser *qmldirParser;
1360     QQmlDirParser **val = m_importQmlDirCache.value(absoluteFilePath);
1361     if (!val) {
1362         qmldirParser = new QQmlDirParser;
1363         qmldirParser->setFileSource(absoluteFilePath);
1364         qmldirParser->setUrl(QUrl::fromLocalFile(absoluteFilePath));
1365         qmldirParser->parse();
1366         m_importQmlDirCache.insert(absoluteFilePath, qmldirParser);
1367     } else {
1368         qmldirParser = *val;
1369     }
1370
1371     return qmldirParser;
1372 }
1373
1374
1375 /*!
1376 Clears cached information about loaded files, including any type data, scripts
1377 and qmldir information.
1378 */
1379 void QQmlTypeLoader::clearCache()
1380 {
1381     for (TypeCache::Iterator iter = m_typeCache.begin(); iter != m_typeCache.end(); ++iter) 
1382         (*iter)->release();
1383     for (ScriptCache::Iterator iter = m_scriptCache.begin(); iter != m_scriptCache.end(); ++iter) 
1384         (*iter)->release();
1385     for (QmldirCache::Iterator iter = m_qmldirCache.begin(); iter != m_qmldirCache.end(); ++iter) 
1386         (*iter)->release();
1387     qDeleteAll(m_importDirCache);
1388     qDeleteAll(m_importQmlDirCache);
1389
1390     m_typeCache.clear();
1391     m_scriptCache.clear();
1392     m_qmldirCache.clear();
1393     m_importDirCache.clear();
1394     m_importQmlDirCache.clear();
1395 }
1396
1397
1398 QQmlTypeData::QQmlTypeData(const QUrl &url, QQmlTypeLoader::Options options, 
1399                                            QQmlTypeLoader *manager)
1400 : QQmlDataBlob(url, QmlFile), m_options(options), m_imports(manager), m_typesResolved(false),
1401    m_compiledData(0), m_typeLoader(manager)
1402 {
1403 }
1404
1405 QQmlTypeData::~QQmlTypeData()
1406 {
1407     for (int ii = 0; ii < m_scripts.count(); ++ii) 
1408         m_scripts.at(ii).script->release();
1409     for (int ii = 0; ii < m_qmldirs.count(); ++ii) 
1410         m_qmldirs.at(ii)->release();
1411     for (int ii = 0; ii < m_types.count(); ++ii) 
1412         if (m_types.at(ii).typeData) m_types.at(ii).typeData->release();
1413     if (m_compiledData)
1414         m_compiledData->release();
1415 }
1416
1417 QQmlTypeLoader *QQmlTypeData::typeLoader() const
1418 {
1419     return m_typeLoader;
1420 }
1421
1422 const QQmlImports &QQmlTypeData::imports() const
1423 {
1424     return m_imports;
1425 }
1426
1427 const QQmlScript::Parser &QQmlTypeData::parser() const
1428 {
1429     return scriptParser;
1430 }
1431
1432 const QList<QQmlTypeData::TypeReference> &QQmlTypeData::resolvedTypes() const
1433 {
1434     return m_types;
1435 }
1436
1437 const QList<QQmlTypeData::ScriptReference> &QQmlTypeData::resolvedScripts() const
1438 {
1439     return m_scripts;
1440 }
1441
1442 const QSet<QString> &QQmlTypeData::namespaces() const
1443 {
1444     return m_namespaces;
1445 }
1446
1447 QQmlCompiledData *QQmlTypeData::compiledData() const
1448 {
1449     if (m_compiledData) 
1450         m_compiledData->addref();
1451
1452     return m_compiledData;
1453 }
1454
1455 void QQmlTypeData::registerCallback(TypeDataCallback *callback)
1456 {
1457     Q_ASSERT(!m_callbacks.contains(callback));
1458     m_callbacks.append(callback);
1459 }
1460
1461 void QQmlTypeData::unregisterCallback(TypeDataCallback *callback)
1462 {
1463     Q_ASSERT(m_callbacks.contains(callback));
1464     m_callbacks.removeOne(callback);
1465     Q_ASSERT(!m_callbacks.contains(callback));
1466 }
1467
1468 void QQmlTypeData::done()
1469 {
1470     // Check all script dependencies for errors
1471     for (int ii = 0; !isError() && ii < m_scripts.count(); ++ii) {
1472         const ScriptReference &script = m_scripts.at(ii);
1473         Q_ASSERT(script.script->isCompleteOrError());
1474         if (script.script->isError()) {
1475             QList<QQmlError> errors = script.script->errors();
1476             QQmlError error;
1477             error.setUrl(finalUrl());
1478             error.setLine(script.location.line);
1479             error.setColumn(script.location.column);
1480             error.setDescription(QQmlTypeLoader::tr("Script %1 unavailable").arg(script.script->url().toString()));
1481             errors.prepend(error);
1482             setError(errors);
1483         }
1484     }
1485
1486     // Check all type dependencies for errors
1487     for (int ii = 0; !isError() && ii < m_types.count(); ++ii) {
1488         const TypeReference &type = m_types.at(ii);
1489         Q_ASSERT(!type.typeData || type.typeData->isCompleteOrError());
1490         if (type.typeData && type.typeData->isError()) {
1491             QString typeName = scriptParser.referencedTypes().at(ii)->name;
1492
1493             QList<QQmlError> errors = type.typeData->errors();
1494             QQmlError error;
1495             error.setUrl(finalUrl());
1496             error.setLine(type.location.line);
1497             error.setColumn(type.location.column);
1498             error.setDescription(QQmlTypeLoader::tr("Type %1 unavailable").arg(typeName));
1499             errors.prepend(error);
1500             setError(errors);
1501         }
1502     }
1503
1504     // Compile component
1505     if (!isError()) 
1506         compile();
1507
1508     if (!(m_options & QQmlTypeLoader::PreserveParser))
1509         scriptParser.clear();
1510 }
1511
1512 void QQmlTypeData::completed()
1513 {
1514     // Notify callbacks
1515     while (!m_callbacks.isEmpty()) {
1516         TypeDataCallback *callback = m_callbacks.takeFirst();
1517         callback->typeDataReady(this);
1518     }
1519 }
1520
1521 void QQmlTypeData::dataReceived(const QByteArray &data)
1522 {
1523     if (!scriptParser.parse(data, finalUrl(), finalUrlString())) {
1524         setError(scriptParser.errors());
1525         return;
1526     }
1527
1528     m_imports.setBaseUrl(finalUrl(), finalUrlString());
1529
1530     foreach (const QQmlScript::Import &import, scriptParser.imports()) {
1531         if (import.type == QQmlScript::Import::File && import.qualifier.isEmpty()) {
1532             QUrl importUrl = finalUrl().resolved(QUrl(import.uri + QLatin1String("/qmldir")));
1533             if (QQmlEnginePrivate::urlToLocalFileOrQrc(importUrl).isEmpty()) {
1534                 QQmlQmldirData *data = typeLoader()->getQmldir(importUrl);
1535                 addDependency(data);
1536                 m_qmldirs << data;
1537             }
1538         } else if (import.type == QQmlScript::Import::Script) {
1539             QUrl scriptUrl = finalUrl().resolved(QUrl(import.uri));
1540             QQmlScriptBlob *blob = typeLoader()->getScript(scriptUrl);
1541             addDependency(blob);
1542
1543             ScriptReference ref;
1544             ref.location = import.location.start;
1545             ref.qualifier = import.qualifier;
1546             ref.script = blob;
1547             m_scripts << ref;
1548
1549         }
1550     }
1551
1552     if (!finalUrl().scheme().isEmpty()) {
1553         QUrl importUrl = finalUrl().resolved(QUrl(QLatin1String("qmldir")));
1554         if (QQmlEnginePrivate::urlToLocalFileOrQrc(importUrl).isEmpty()) {
1555             QQmlQmldirData *data = typeLoader()->getQmldir(importUrl);
1556             addDependency(data);
1557             m_qmldirs << data;
1558         }
1559     }
1560 }
1561
1562 void QQmlTypeData::allDependenciesDone()
1563 {
1564     if (!m_typesResolved) {
1565         resolveTypes();
1566         m_typesResolved = true;
1567     }
1568 }
1569
1570 void QQmlTypeData::downloadProgressChanged(qreal p)
1571 {
1572     for (int ii = 0; ii < m_callbacks.count(); ++ii) {
1573         TypeDataCallback *callback = m_callbacks.at(ii);
1574         callback->typeDataProgress(this, p);
1575     }
1576 }
1577
1578 void QQmlTypeData::compile()
1579 {
1580     Q_ASSERT(m_compiledData == 0);
1581
1582     m_compiledData = new QQmlCompiledData(typeLoader()->engine());
1583     m_compiledData->url = finalUrl();
1584     m_compiledData->name = finalUrlString();
1585
1586     QQmlCompilingProfiler prof(m_compiledData->name);
1587
1588     QQmlCompiler compiler(&scriptParser._pool);
1589     if (!compiler.compile(typeLoader()->engine(), this, m_compiledData)) {
1590         setError(compiler.errors());
1591         m_compiledData->release();
1592         m_compiledData = 0;
1593     }
1594 }
1595
1596 void QQmlTypeData::resolveTypes()
1597 {
1598     QQmlEnginePrivate *ep = QQmlEnginePrivate::get(m_typeLoader->engine());
1599     QQmlImportDatabase *importDatabase = &ep->importDatabase;
1600
1601     // For local urls, add an implicit import "." as first (most overridden) lookup. 
1602     // This will also trigger the loading of the qmldir and the import of any native 
1603     // types from available plugins.
1604     QList<QQmlError> errors;
1605     if (QQmlQmldirData *qmldir = qmldirForUrl(finalUrl().resolved(QUrl(QLatin1String("./qmldir"))))) {
1606         m_imports.addImport(importDatabase, QLatin1String("."),
1607                             QString(), -1, -1, QQmlScript::Import::File, 
1608                             qmldir->dirComponents(), &errors);
1609     } else {
1610         m_imports.addImport(importDatabase, QLatin1String("."), 
1611                             QString(), -1, -1, QQmlScript::Import::File, 
1612                             QQmlDirComponents(), &errors);
1613     }
1614
1615     // remove any errors which are due to the implicit import which aren't real errors.
1616     // for example, if the implicitly included qmldir file doesn't exist, that is not an error.
1617     QList<QQmlError> realErrors;
1618     for (int i = 0; i < errors.size(); ++i) {
1619         if (errors.at(i).description() != QQmlImportDatabase::tr("import \".\" has no qmldir and no namespace")
1620                 && errors.at(i).description() != QQmlImportDatabase::tr("\".\": no such directory")) {
1621             realErrors.prepend(errors.at(i)); // this is a real error.
1622         }
1623     }
1624
1625     // report any real errors which occurred during plugin loading or qmldir parsing.
1626     if (!realErrors.isEmpty()) {
1627         setError(realErrors);
1628         return;
1629     }
1630
1631     foreach (const QQmlScript::Import &import, scriptParser.imports()) {
1632         QQmlDirComponents qmldircomponentsnetwork;
1633         if (import.type == QQmlScript::Import::Script)
1634             continue;
1635
1636         if (import.type == QQmlScript::Import::File && import.qualifier.isEmpty()) {
1637             QUrl qmldirUrl = finalUrl().resolved(QUrl(import.uri + QLatin1String("/qmldir")));
1638             if (QQmlQmldirData *qmldir = qmldirForUrl(qmldirUrl))
1639                 qmldircomponentsnetwork = qmldir->dirComponents();
1640         }
1641
1642         int vmaj = -1;
1643         int vmin = -1;
1644         import.extractVersion(&vmaj, &vmin);
1645
1646         QList<QQmlError> errors;
1647         if (!m_imports.addImport(importDatabase, import.uri, import.qualifier,
1648                                  vmaj, vmin, import.type, qmldircomponentsnetwork, &errors)) {
1649             QQmlError error;
1650             if (errors.size()) {
1651                 error = errors.takeFirst();
1652             } else {
1653                 // this should not be possible!
1654                 // Description should come from error provided by addImport() function.
1655                 error.setDescription(QQmlTypeLoader::tr("Unreported error adding script import to import database"));
1656             }
1657             error.setUrl(m_imports.baseUrl());
1658             error.setLine(import.location.start.line);
1659             error.setColumn(import.location.start.column);
1660             errors.prepend(error); // put it back on the list after filling out information.
1661
1662             setError(errors);
1663             return;
1664         }
1665     }
1666
1667     // Add any imported scripts to our resolved set
1668     foreach (const QQmlImports::ScriptReference &script, m_imports.resolvedScripts())
1669     {
1670         QQmlScriptBlob *blob = typeLoader()->getScript(script.location);
1671         addDependency(blob);
1672
1673         ScriptReference ref;
1674         //ref.location = ...
1675         ref.qualifier = script.nameSpace;
1676         if (!script.qualifier.isEmpty())
1677         {
1678             ref.qualifier.prepend(script.qualifier + QLatin1Char('.'));
1679
1680             // Add a reference to the enclosing namespace
1681             m_namespaces.insert(script.qualifier);
1682         }
1683
1684         ref.script = blob;
1685         m_scripts << ref;
1686     }
1687
1688     foreach (QQmlScript::TypeReference *parserRef, scriptParser.referencedTypes()) {
1689         TypeReference ref;
1690
1691         QString url;
1692         int majorVersion;
1693         int minorVersion;
1694         QQmlImportedNamespace *typeNamespace = 0;
1695         QList<QQmlError> errors;
1696
1697         if (!m_imports.resolveType(parserRef->name, &ref.type, &url, &majorVersion, &minorVersion,
1698                                    &typeNamespace, &errors) || typeNamespace) {
1699             // Known to not be a type:
1700             //  - known to be a namespace (Namespace {})
1701             //  - type with unknown namespace (UnknownNamespace.SomeType {})
1702             QQmlError error;
1703             QString userTypeName = parserRef->name;
1704             userTypeName.replace(QLatin1Char('/'),QLatin1Char('.'));
1705             if (typeNamespace) {
1706                 error.setDescription(QQmlTypeLoader::tr("Namespace %1 cannot be used as a type").arg(userTypeName));
1707             } else {
1708                 if (errors.size()) {
1709                     error = errors.takeFirst();
1710                 } else {
1711                     // this should not be possible!
1712                     // Description should come from error provided by addImport() function.
1713                     error.setDescription(QQmlTypeLoader::tr("Unreported error adding script import to import database"));
1714                 }
1715                 error.setUrl(m_imports.baseUrl());
1716                 error.setDescription(QQmlTypeLoader::tr("%1 %2").arg(userTypeName).arg(error.description()));
1717             }
1718
1719             if (!parserRef->refObjects.isEmpty()) {
1720                 QQmlScript::Object *obj = parserRef->refObjects.first();
1721                 error.setLine(obj->location.start.line);
1722                 error.setColumn(obj->location.start.column);
1723             }
1724
1725             errors.prepend(error);
1726             setError(errors);
1727             return;
1728         }
1729
1730         if (ref.type) {
1731             ref.majorVersion = majorVersion;
1732             ref.minorVersion = minorVersion;
1733         } else {
1734             ref.typeData = typeLoader()->get(QUrl(url));
1735             addDependency(ref.typeData);
1736         }
1737
1738         if (parserRef->refObjects.count())
1739             ref.location = parserRef->refObjects.first()->location.start;
1740
1741         m_types << ref;
1742     }
1743 }
1744
1745 QQmlQmldirData *QQmlTypeData::qmldirForUrl(const QUrl &url)
1746 {
1747     for (int ii = 0; ii < m_qmldirs.count(); ++ii) {
1748         if (m_qmldirs.at(ii)->url() == url)
1749             return m_qmldirs.at(ii);
1750     }
1751     return 0;
1752 }
1753
1754 QQmlScriptData::QQmlScriptData()
1755 : importCache(0), pragmas(QQmlScript::Object::ScriptBlock::None), m_loaded(false) 
1756 {
1757 }
1758
1759 QQmlScriptData::~QQmlScriptData()
1760 {
1761 }
1762
1763 void QQmlScriptData::clear()
1764 {
1765     if (importCache) {
1766         importCache->release();
1767         importCache = 0;
1768     }
1769
1770     for (int ii = 0; ii < scripts.count(); ++ii)
1771         scripts.at(ii)->release();
1772     scripts.clear();
1773
1774     qPersistentDispose(m_program);
1775     qPersistentDispose(m_value);
1776
1777     // An addref() was made when the QQmlCleanup was added to the engine.
1778     release();
1779 }
1780
1781 QQmlScriptBlob::QQmlScriptBlob(const QUrl &url, QQmlTypeLoader *loader)
1782 : QQmlDataBlob(url, JavaScriptFile), m_pragmas(QQmlScript::Object::ScriptBlock::None),
1783   m_imports(loader), m_scriptData(0), m_typeLoader(loader)
1784 {
1785 }
1786
1787 QQmlScriptBlob::~QQmlScriptBlob()
1788 {
1789     if (m_scriptData) {
1790         m_scriptData->release();
1791         m_scriptData = 0;
1792     }
1793 }
1794
1795 QQmlScript::Object::ScriptBlock::Pragmas QQmlScriptBlob::pragmas() const
1796 {
1797     return m_pragmas;
1798 }
1799
1800 QQmlTypeLoader *QQmlScriptBlob::typeLoader() const
1801 {
1802     return m_typeLoader;
1803 }
1804
1805 const QQmlImports &QQmlScriptBlob::imports() const
1806 {
1807     return m_imports;
1808 }
1809
1810 QQmlScriptData *QQmlScriptBlob::scriptData() const
1811 {
1812     return m_scriptData;
1813 }
1814
1815 void QQmlScriptBlob::dataReceived(const QByteArray &data)
1816 {
1817     QQmlEnginePrivate *ep = QQmlEnginePrivate::get(m_typeLoader->engine());
1818     QQmlImportDatabase *importDatabase = &ep->importDatabase;
1819
1820     m_source = QString::fromUtf8(data);
1821
1822     QQmlScript::Parser::JavaScriptMetaData metadata =
1823         QQmlScript::Parser::extractMetaData(m_source);
1824
1825     m_imports.setBaseUrl(finalUrl(), finalUrlString());
1826
1827     m_pragmas = metadata.pragmas;
1828
1829     foreach (const QQmlScript::Import &import, metadata.imports) {
1830         Q_ASSERT(import.type != QQmlScript::Import::File);
1831
1832         if (import.type == QQmlScript::Import::Script) {
1833             QUrl scriptUrl = finalUrl().resolved(QUrl(import.uri));
1834             QQmlScriptBlob *blob = typeLoader()->getScript(scriptUrl);
1835             addDependency(blob);
1836
1837             ScriptReference ref;
1838             ref.location = import.location.start;
1839             ref.qualifier = import.qualifier;
1840             ref.script = blob;
1841             m_scripts << ref;
1842         } else {
1843             Q_ASSERT(import.type == QQmlScript::Import::Library);
1844             int vmaj = -1;
1845             int vmin = -1;
1846             import.extractVersion(&vmaj, &vmin);
1847
1848             QList<QQmlError> errors;
1849             if (!m_imports.addImport(importDatabase, import.uri, import.qualifier, vmaj, vmin,
1850                                      import.type, QQmlDirComponents(), &errors)) {
1851                 QQmlError error = errors.takeFirst();
1852                 // description should be set by addImport().
1853                 error.setUrl(m_imports.baseUrl());
1854                 error.setLine(import.location.start.line);
1855                 error.setColumn(import.location.start.column);
1856                 errors.prepend(error);
1857
1858                 setError(errors);
1859                 return;
1860             }
1861         }
1862     }
1863 }
1864
1865 void QQmlScriptBlob::done()
1866 {
1867     // Check all script dependencies for errors
1868     for (int ii = 0; !isError() && ii < m_scripts.count(); ++ii) {
1869         const ScriptReference &script = m_scripts.at(ii);
1870         Q_ASSERT(script.script->isCompleteOrError());
1871         if (script.script->isError()) {
1872             QList<QQmlError> errors = script.script->errors();
1873             QQmlError error;
1874             error.setUrl(finalUrl());
1875             error.setLine(script.location.line);
1876             error.setColumn(script.location.column);
1877             error.setDescription(typeLoader()->tr("Script %1 unavailable").arg(script.script->url().toString()));
1878             errors.prepend(error);
1879             setError(errors);
1880         }
1881     }
1882
1883     if (isError())
1884         return;
1885
1886     QQmlEngine *engine = typeLoader()->engine();
1887     m_scriptData = new QQmlScriptData();
1888     m_scriptData->url = finalUrl();
1889     m_scriptData->urlString = finalUrlString();
1890     m_scriptData->importCache = new QQmlTypeNameCache();
1891
1892     for (int ii = 0; !isError() && ii < m_scripts.count(); ++ii) {
1893         const ScriptReference &script = m_scripts.at(ii);
1894
1895         m_scriptData->scripts.append(script.script);
1896         m_scriptData->importCache->add(script.qualifier, ii);
1897     }
1898
1899     m_imports.populateCache(m_scriptData->importCache, engine);
1900
1901     m_scriptData->pragmas = m_pragmas;
1902     m_scriptData->m_programSource = m_source.toUtf8();
1903     m_source.clear();
1904 }
1905
1906 QQmlQmldirData::QQmlQmldirData(const QUrl &url)
1907 : QQmlDataBlob(url, QmldirFile)
1908 {
1909 }
1910
1911 const QQmlDirComponents &QQmlQmldirData::dirComponents() const
1912 {
1913     return m_components;
1914 }
1915
1916 void QQmlQmldirData::dataReceived(const QByteArray &data)
1917 {
1918     QQmlDirParser parser;
1919     parser.setSource(QString::fromUtf8(data));
1920     parser.parse();
1921     m_components = parser.components();
1922 }
1923
1924 QT_END_NAMESPACE
1925
1926 #include "qqmltypeloader.moc"