Initial bundle support
[profile/ivi/qtdeclarative.git] / src / qml / qml / qqmlfile.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 "qqmlfile.h"
43
44 #include <QtCore/qurl.h>
45 #include <QtCore/qobject.h>
46 #include <private/qqmlengine_p.h>
47 #include <private/qqmlglobal_p.h>
48
49 /*!
50 \class The QQmlFile class gives access to local and remote files.
51
52 Supports file://, qrc://, bundle:// uris and whatever QNetworkAccessManager supports.
53 */
54
55 #define QQMLFILE_MAX_REDIRECT_RECURSION 16
56
57 QT_BEGIN_NAMESPACE
58
59 static QString qrc_string(QLatin1String("qrc"));
60 static QString file_string(QLatin1String("file"));
61 static QString bundle_string(QLatin1String("bundle"));
62
63 class QQmlFilePrivate;
64 class QQmlFileNetworkReply : public QObject
65 {
66 Q_OBJECT
67 public:
68     QQmlFileNetworkReply(QQmlEngine *, QQmlFilePrivate *, const QUrl &);
69     ~QQmlFileNetworkReply();
70
71 signals:
72     void finished();
73     void downloadProgress(qint64, qint64);
74
75 public slots:
76     void networkFinished();
77     void networkDownloadProgress(qint64, qint64);
78
79 public:
80     static int finishedIndex;
81     static int downloadProgressIndex;
82     static int networkFinishedIndex;
83     static int networkDownloadProgressIndex;
84     static int replyFinishedIndex;
85     static int replyDownloadProgressIndex;
86
87 private:
88     QQmlEngine *m_engine;
89     QQmlFilePrivate *m_p;
90
91     int m_redirectCount;
92     QNetworkReply *m_reply;
93 };
94
95 class QQmlFilePrivate
96 {
97 public:
98     QQmlFilePrivate();
99
100     mutable QUrl url;
101     mutable QString urlString;
102
103     QQmlBundleData *bundle;
104     const QQmlBundle::FileEntry *file;
105
106     QByteArray data;
107
108     enum Error {
109         None, NotFound, CaseMismatch, Network
110     };
111
112     Error error;
113     QString errorString;
114
115     QQmlFileNetworkReply *reply;
116 };
117
118 int QQmlFileNetworkReply::finishedIndex = -1;
119 int QQmlFileNetworkReply::downloadProgressIndex = -1;
120 int QQmlFileNetworkReply::networkFinishedIndex = -1;
121 int QQmlFileNetworkReply::networkDownloadProgressIndex = -1;
122 int QQmlFileNetworkReply::replyFinishedIndex = -1;
123 int QQmlFileNetworkReply::replyDownloadProgressIndex = -1;
124
125 QQmlFileNetworkReply::QQmlFileNetworkReply(QQmlEngine *e, QQmlFilePrivate *p, const QUrl &url)
126 : m_engine(e), m_p(p), m_redirectCount(0), m_reply(0)
127 {
128     if (finishedIndex == -1) {
129         const QMetaObject *smo = &staticMetaObject;
130         finishedIndex = smo->indexOfSignal("finished()");
131         downloadProgressIndex = smo->indexOfSignal("downloadProgress(qint64,qint64)");
132         networkFinishedIndex = smo->indexOfMethod("networkFinished()");
133         networkDownloadProgressIndex = smo->indexOfMethod("networkDownloadProgress(qint64,qint64)");
134
135         const QMetaObject *rsmo = &QNetworkReply::staticMetaObject;
136         replyFinishedIndex = rsmo->indexOfSignal("finished()");
137         replyDownloadProgressIndex = rsmo->indexOfSignal("downloadProgress(qint64,qint64)");
138     }
139     Q_ASSERT(finishedIndex != -1 && downloadProgressIndex != -1 &&
140              networkFinishedIndex != -1 && networkDownloadProgressIndex != -1 &&
141              replyFinishedIndex != -1 && replyDownloadProgressIndex != -1);
142
143     QNetworkRequest req(url);
144     req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
145
146     m_reply = m_engine->networkAccessManager()->get(req);
147     QMetaObject::connect(m_reply, replyFinishedIndex, this, networkFinishedIndex);
148     QMetaObject::connect(m_reply, replyDownloadProgressIndex, this, networkDownloadProgressIndex);
149 }
150
151 QQmlFileNetworkReply::~QQmlFileNetworkReply()
152 {
153     if (m_reply) {
154         m_reply->disconnect();
155         m_reply->deleteLater();
156     }
157 }
158
159 void QQmlFileNetworkReply::networkFinished()
160 {
161     ++m_redirectCount;
162     if (m_redirectCount < QQMLFILE_MAX_REDIRECT_RECURSION) {
163         QVariant redirect = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
164         if (redirect.isValid()) {
165             QUrl url = m_reply->url().resolved(redirect.toUrl());
166
167             QNetworkRequest req(url);
168             req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
169
170             m_reply->deleteLater();
171             m_reply = m_engine->networkAccessManager()->get(req);
172
173             QMetaObject::connect(m_reply, replyFinishedIndex,
174                                  this, networkFinishedIndex);
175             QMetaObject::connect(m_reply, replyDownloadProgressIndex,
176                                  this, networkDownloadProgressIndex);
177
178             return;
179         }
180     }
181
182     if (m_reply->error()) {
183         m_p->errorString = m_reply->errorString();
184         m_p->error = QQmlFilePrivate::Network;
185     } else {
186         m_p->data = m_reply->readAll();
187     }
188
189     m_reply->deleteLater();
190     m_reply = 0;
191
192     m_p->reply = 0;
193     emit finished();
194     delete this;
195 }
196
197 void QQmlFileNetworkReply::networkDownloadProgress(qint64 a, qint64 b)
198 {
199     emit downloadProgress(a, b);
200 }
201
202 QQmlFilePrivate::QQmlFilePrivate()
203 : bundle(0), file(0), error(None), reply(0)
204 {
205 }
206
207 QQmlFile::QQmlFile()
208 : d(new QQmlFilePrivate)
209 {
210 }
211
212 QQmlFile::QQmlFile(QQmlEngine *e, const QUrl &url)
213 : d(new QQmlFilePrivate)
214 {
215     load(e, url);
216 }
217
218 QQmlFile::QQmlFile(QQmlEngine *e, const QString &url)
219 : d(new QQmlFilePrivate)
220 {
221     load(e, url);
222 }
223
224 QQmlFile::~QQmlFile()
225 {
226     if (d->reply)
227         delete d->reply;
228     if (d->bundle)
229         d->bundle->release();
230
231     delete d;
232     d = 0;
233 }
234
235 bool QQmlFile::isNull() const
236 {
237     return status() == Null;
238 }
239
240 bool QQmlFile::isReady() const
241 {
242     return status() == Ready;
243 }
244
245 bool QQmlFile::isError() const
246 {
247     return status() == Error;
248 }
249
250 bool QQmlFile::isLoading() const
251 {
252     return status() == Loading;
253 }
254
255 QUrl QQmlFile::url() const
256 {
257     if (!d->urlString.isEmpty()) {
258         d->url = QUrl(d->urlString);
259         d->urlString = QString();
260     }
261     return d->url;
262 }
263
264 QQmlFile::Status QQmlFile::status() const
265 {
266     if (d->url.isEmpty() && d->urlString.isEmpty())
267         return Null;
268     else if (d->reply)
269         return Loading;
270     else if (d->error != QQmlFilePrivate::None)
271         return Error;
272     else
273         return Ready;
274 }
275
276 QString QQmlFile::error() const
277 {
278     switch (d->error) {
279     default:
280     case QQmlFilePrivate::None:
281         return QString();
282     case QQmlFilePrivate::NotFound:
283         return QLatin1String("File not found");
284     case QQmlFilePrivate::CaseMismatch:
285         return QLatin1String("File name case mismatch");
286     }
287 }
288
289 qint64 QQmlFile::size() const
290 {
291     if (d->file) return d->file->fileSize();
292     else return d->data.size();
293 }
294
295 const char *QQmlFile::data() const
296 {
297     if (d->file) return d->file->contents();
298     else return d->data.constData();
299 }
300
301 QByteArray QQmlFile::dataByteArray() const
302 {
303     if (d->file) return QByteArray(d->file->contents(), d->file->fileSize());
304     else return d->data;
305 }
306
307 QByteArray QQmlFile::metaData(const QString &name) const
308 {
309     if (d->file) {
310         Q_ASSERT(d->bundle);
311         const QQmlBundle::FileEntry *meta = d->bundle->link(d->file, name);
312         if (meta)
313             return QByteArray::fromRawData(meta->contents(), meta->fileSize());
314     }
315     return QByteArray();
316 }
317
318 void QQmlFile::load(QQmlEngine *engine, const QUrl &url)
319 {
320     Q_ASSERT(engine);
321
322     QString scheme = url.scheme();
323
324     clear();
325     d->url = url;
326
327     if (isBundle(url)) {
328         // Bundle
329         QQmlEnginePrivate *p = QQmlEnginePrivate::get(engine);
330         QQmlBundleData *bundle = p->typeLoader.getBundle(url.host());
331
332         d->error = QQmlFilePrivate::NotFound;
333
334         if (bundle) {
335             QString filename = url.path().mid(1);
336             const QQmlBundle::FileEntry *entry = bundle->find(filename);
337             if (entry) {
338                 d->file = entry;
339                 d->bundle = bundle;
340                 d->bundle->addref();
341                 d->error = QQmlFilePrivate::None;
342             }
343             bundle->release();
344         }
345     } else if (isLocalFile(url)) {
346         QString lf = urlToLocalFileOrQrc(url);
347
348         if (!QQml_isFileCaseCorrect(lf)) {
349             d->error = QQmlFilePrivate::CaseMismatch;
350             return;
351         }
352
353         QFile file(lf);
354         if (file.open(QFile::ReadOnly)) {
355             d->data = file.readAll();
356         } else {
357             d->error = QQmlFilePrivate::NotFound;
358         }
359     } else {
360         d->reply = new QQmlFileNetworkReply(engine, d, url);
361     }
362 }
363
364 void QQmlFile::load(QQmlEngine *engine, const QString &url)
365 {
366     Q_ASSERT(engine);
367
368     clear();
369
370     d->urlString = url;
371
372     if (isBundle(url)) {
373         // Bundle
374         QQmlEnginePrivate *p = QQmlEnginePrivate::get(engine);
375
376         d->error = QQmlFilePrivate::NotFound;
377
378         int index = url.indexOf(QLatin1Char('/'), 9);
379         if (index == -1)
380             return;
381
382         QStringRef identifier(&url, 9, index - 9);
383
384         QQmlBundleData *bundle = p->typeLoader.getBundle(identifier);
385
386         d->error = QQmlFilePrivate::NotFound;
387
388         if (bundle) {
389             QString filename = url.mid(index);
390             const QQmlBundle::FileEntry *entry = bundle->find(filename);
391             if (entry) {
392                 d->data = QByteArray(entry->contents(), entry->fileSize());
393                 d->error = QQmlFilePrivate::None;
394             }
395             bundle->release();
396         }
397
398     } else if (isLocalFile(url)) {
399         QString lf = urlToLocalFileOrQrc(url);
400
401         if (!QQml_isFileCaseCorrect(lf)) {
402             d->error = QQmlFilePrivate::CaseMismatch;
403             return;
404         }
405
406         QFile file(lf);
407         if (file.open(QFile::ReadOnly)) {
408             d->data = file.readAll();
409         } else {
410             d->error = QQmlFilePrivate::NotFound;
411         }
412     } else {
413         QUrl qurl(url);
414         d->url = qurl;
415         d->urlString = QString();
416         d->reply = new QQmlFileNetworkReply(engine, d, qurl);
417     }
418 }
419
420 void QQmlFile::clear()
421 {
422     d->url = QUrl();
423     d->urlString = QString();
424     d->data = QByteArray();
425     if (d->bundle) d->bundle->release();
426     d->bundle = 0;
427     d->file = 0;
428     d->error = QQmlFilePrivate::None;
429 }
430
431 void QQmlFile::clear(QObject *)
432 {
433     clear();
434 }
435
436 bool QQmlFile::connectFinished(QObject *object, const char *method)
437 {
438     if (!d || !d->reply) {
439         qWarning("QQmlFile: connectFinished() called when not loading.");
440         return false;
441     }
442
443     return QObject::connect(d->reply, SIGNAL(finished()),
444                             object, method);
445 }
446
447 bool QQmlFile::connectFinished(QObject *object, int method)
448 {
449     if (!d || !d->reply) {
450         qWarning("QQmlFile: connectFinished() called when not loading.");
451         return false;
452     }
453
454     return QMetaObject::connect(d->reply, QQmlFileNetworkReply::finishedIndex,
455                                 object, method);
456 }
457
458 bool QQmlFile::connectDownloadProgress(QObject *object, const char *method)
459 {
460     if (!d || !d->reply) {
461         qWarning("QQmlFile: connectDownloadProgress() called when not loading.");
462         return false;
463     }
464
465     return QObject::connect(d->reply, SIGNAL(downloadProgress(qint64,qint64)),
466                             object, method);
467 }
468
469 bool QQmlFile::connectDownloadProgress(QObject *object, int method)
470 {
471     if (!d || !d->reply) {
472         qWarning("QQmlFile: connectDownloadProgress() called when not loading.");
473         return false;
474     }
475
476     return QMetaObject::connect(d->reply, QQmlFileNetworkReply::downloadProgressIndex,
477                                 object, method);
478 }
479
480 /*!
481 Returns true if QQmlFile will open \a url synchronously.
482
483 Synchronous urls have a qrc://, file://, or bundle:// scheme.
484 */
485 bool QQmlFile::isSynchronous(const QUrl &url)
486 {
487     QString scheme = url.scheme();
488
489     if ((scheme.length() == 4 && 0 == scheme.compare(file_string, Qt::CaseInsensitive)) ||
490         (scheme.length() == 6 && 0 == scheme.compare(bundle_string, Qt::CaseInsensitive)) ||
491         (scheme.length() == 3 && 0 == scheme.compare(qrc_string, Qt::CaseInsensitive)))
492         return true;
493     else
494         return false;
495 }
496
497 /*!
498 Returns true if QQmlFile will open \a url synchronously.
499
500 Synchronous urls have a qrc://, file://, or bundle:// scheme.
501 */
502 bool QQmlFile::isSynchronous(const QString &url)
503 {
504     if (url.length() < 6 /* qrc:// */)
505         return false;
506
507     QChar f = url[0];
508
509     if (f == QLatin1Char('f') || f == QLatin1Char('F')) {
510
511         return url.length() >= 7 /* file:// */ &&
512                url.startsWith(file_string, Qt::CaseInsensitive) &&
513                url[4] == QLatin1Char(':') && url[5] == QLatin1Char('/') && url[6] == QLatin1Char('/');
514
515     } else if (f == QLatin1Char('b') || f == QLatin1Char('B')) {
516
517         return url.length() >= 9 /* bundle:// */ &&
518                url.startsWith(bundle_string, Qt::CaseInsensitive) &&
519                url[6] == QLatin1Char(':') && url[7] == QLatin1Char('/') && url[8] == QLatin1Char('/');
520
521     } else if (f == QLatin1Char('q') || f == QLatin1Char('Q')) {
522
523         return url.length() >= 6 /* bundle:// */ &&
524                url.startsWith(qrc_string, Qt::CaseInsensitive) &&
525                url[3] == QLatin1Char(':') && url[4] == QLatin1Char('/') && url[5] == QLatin1Char('/');
526
527     }
528
529     return false;
530 }
531
532 /*!
533 Returns true if \a url is a bundle.
534
535 Bundle urls have a bundle:// scheme.
536 */
537 bool QQmlFile::isBundle(const QString &url)
538 {
539     return url.length() >= 9 && url.startsWith(bundle_string, Qt::CaseInsensitive) &&
540            url[6] == QLatin1Char(':') && url[7] == QLatin1Char('/') && url[8] == QLatin1Char('/');
541 }
542
543 /*!
544 Returns true if \a url is a bundle.
545
546 Bundle urls have a bundle:// scheme.
547 */
548 bool QQmlFile::isBundle(const QUrl &url)
549 {
550     QString scheme = url.scheme();
551
552     return scheme.length() == 6 && 0 == scheme.compare(bundle_string, Qt::CaseInsensitive);
553 }
554
555 /*!
556 Returns true if \a url is a local file that can be opened with QFile.
557
558 Local file urls have either a qrc:// or file:// scheme.
559 */
560 bool QQmlFile::isLocalFile(const QUrl &url)
561 {
562     QString scheme = url.scheme();
563
564     if ((scheme.length() == 4 && 0 == scheme.compare(file_string, Qt::CaseInsensitive)) ||
565         (scheme.length() == 3 && 0 == scheme.compare(qrc_string, Qt::CaseInsensitive)))
566         return true;
567     else
568         return false;
569 }
570
571 /*!
572 Returns true if \a url is a local file that can be opened with QFile.
573
574 Local file urls have either a qrc:// or file:// scheme.
575 */
576 bool QQmlFile::isLocalFile(const QString &url)
577 {
578     if (url.length() < 6 /* qrc:// */)
579         return false;
580
581     QChar f = url[0];
582
583     if (f == QLatin1Char('f') || f == QLatin1Char('F')) {
584
585         return url.length() >= 7 /* file:// */ &&
586                url.startsWith(file_string, Qt::CaseInsensitive) &&
587                url[4] == QLatin1Char(':') && url[5] == QLatin1Char('/') && url[6] == QLatin1Char('/');
588
589     } else if (f == QLatin1Char('q') || f == QLatin1Char('Q')) {
590
591         return url.length() >= 6 /* bundle:// */ &&
592                url.startsWith(qrc_string, Qt::CaseInsensitive) &&
593                url[3] == QLatin1Char(':') && url[4] == QLatin1Char('/') && url[5] == QLatin1Char('/');
594
595     }
596
597     return false;
598 }
599
600 /*!
601 If \a url is a local file returns a path suitable for passing to QFile.  Otherwise returns an
602 empty string.
603 */
604 QString QQmlFile::urlToLocalFileOrQrc(const QUrl& url)
605 {
606     if (url.scheme().compare(QLatin1String("qrc"), Qt::CaseInsensitive) == 0) {
607         if (url.authority().isEmpty())
608             return QLatin1Char(':') + url.path();
609         return QString();
610     }
611     return url.toLocalFile();
612 }
613
614 static QString toLocalFile(const QString &url)
615 {
616     if (!url.startsWith(QLatin1String("file://"), Qt::CaseInsensitive))
617         return QString();
618
619     QString file = url.mid(7);
620
621     //XXX TODO: handle windows hostnames: "//servername/path/to/file.txt"
622
623     // magic for drives on windows
624     if (file.length() > 2 && file.at(0) == QLatin1Char('/') && file.at(2) == QLatin1Char(':'))
625         file.remove(0, 1);
626
627     return file;
628 }
629
630 /*!
631 If \a url is a local file returns a path suitable for passing to QFile.  Otherwise returns an
632 empty string.
633 */
634 QString QQmlFile::urlToLocalFileOrQrc(const QString& url)
635 {
636     if (url.startsWith(QLatin1String("qrc:"), Qt::CaseInsensitive)) {
637         if (url.length() > 4)
638             return QLatin1Char(':') + url.mid(4);
639         return QString();
640     }
641
642     return toLocalFile(url);
643 }
644
645 bool QQmlFile::bundleDirectoryExists(const QString &dir, QQmlEngine *e)
646 {
647     if (!isBundle(dir))
648         return false;
649
650     int index = dir.indexOf(QLatin1Char('/'), 9);
651
652     if (index == -1 && dir.length() > 9) // We accept "bundle://<blah>" with no extra path
653         index = dir.length();
654
655     if (index == -1)
656         return false;
657
658     QStringRef identifier(&dir, 9, index - 9);
659     QStringRef path(&dir, index + 1, dir.length() - index - 1);
660
661     QQmlBundleData *bundle = QQmlEnginePrivate::get(e)->typeLoader.getBundle(identifier);
662
663     if (bundle) {
664         int lastIndex = dir.lastIndexOf(QLatin1Char('/'));
665
666         if (lastIndex <= index) {
667             bundle->release();
668             return true;
669         }
670
671         QStringRef d(&dir, index + 1, lastIndex - index);
672
673         QList<const QQmlBundle::FileEntry *> entries = bundle->files();
674
675         for (int ii = 0; ii < entries.count(); ++ii) {
676             QString name = entries.at(ii)->fileName();
677             if (name.startsWith(d)) {
678                 bundle->release();
679                 return true;
680             }
681         }
682
683         bundle->release();
684     }
685
686     return false;
687 }
688
689 bool QQmlFile::bundleDirectoryExists(const QUrl &url, QQmlEngine *e)
690 {
691     if (!isBundle(url))
692         return false;
693
694     QQmlBundleData *bundle = QQmlEnginePrivate::get(e)->typeLoader.getBundle(url.host());
695
696     if (bundle) {
697         QString path = url.path();
698
699         int lastIndex = path.lastIndexOf(QLatin1Char('/'));
700
701         if (lastIndex == -1) {
702             bundle->release();
703             return true;
704         }
705
706         QStringRef d(&path, 0, lastIndex);
707
708         QList<const QQmlBundle::FileEntry *> entries = bundle->files();
709
710         for (int ii = 0; ii < entries.count(); ++ii) {
711             QString name = entries.at(ii)->fileName();
712             if (name.startsWith(d)) {
713                 bundle->release();
714                 return true;
715             }
716         }
717
718         bundle->release();
719     }
720
721     return false;
722 }
723
724 bool QQmlFile::bundleFileExists(const QString &file, QQmlEngine *e)
725 {
726     if (!isBundle(file))
727         return false;
728
729     int index = file.indexOf(QLatin1Char('/'), 9);
730
731     if (index == -1)
732         return false;
733
734     QStringRef identifier(&file, 9, index - 9);
735     QStringRef path(&file, index + 1, file.length() - index - 1);
736
737     QQmlBundleData *bundle = QQmlEnginePrivate::get(e)->typeLoader.getBundle(identifier);
738
739     if (bundle) {
740         const QQmlBundle::FileEntry *entry = bundle->find(path.constData(), path.length());
741         bundle->release();
742
743         return entry != 0;
744     }
745
746     return false;
747 }
748
749 bool QQmlFile::bundleFileExists(const QUrl &, QQmlEngine *)
750 {
751     qFatal("Not implemented");
752     return true;
753 }
754
755 /*!
756 Returns the file name for the bundle file referenced by \a url or an
757 empty string if \a url isn't a bundle url.
758 */
759 QString QQmlFile::bundleFileName(const QString &url, QQmlEngine *e)
760 {
761     if (!isBundle(url))
762         return QString();
763
764     int index = url.indexOf(QLatin1Char('/'), 9);
765
766     if (index == -1)
767         index = url.length();
768
769     QStringRef identifier(&url, 9, index - 9);
770
771     QQmlBundleData *bundle = QQmlEnginePrivate::get(e)->typeLoader.getBundle(identifier);
772
773     if (bundle) {
774         QString rv = bundle->fileName;
775         bundle->release();
776         return rv;
777     }
778
779     return QString();
780 }
781
782 /*!
783 Returns the file name for the bundle file referenced by \a url or an
784 empty string if \a url isn't a bundle url.
785 */
786 QString QQmlFile::bundleFileName(const QUrl &url, QQmlEngine *e)
787 {
788     if (!isBundle(url))
789         return QString();
790
791     QQmlBundleData *bundle = QQmlEnginePrivate::get(e)->typeLoader.getBundle(url.host());
792
793     if (bundle) {
794         QString rv = bundle->fileName;
795         bundle->release();
796         return rv;
797     }
798
799     return QString();
800 }
801
802 QT_END_NAMESPACE
803
804 #include "qqmlfile.moc"