1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtQml module of the Qt Toolkit.
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.
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.
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.
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.
40 ****************************************************************************/
44 #include <QtCore/qurl.h>
45 #include <QtCore/qobject.h>
46 #include <QtCore/qmetaobject.h>
47 #include <private/qqmlengine_p.h>
48 #include <private/qqmlglobal_p.h>
52 \brief The QQmlFile class gives access to local and remote files.
54 Supports file://, qrc:/, bundle:// uris and whatever QNetworkAccessManager supports.
57 #define QQMLFILE_MAX_REDIRECT_RECURSION 16
61 static QString qrc_string(QLatin1String("qrc"));
62 static QString file_string(QLatin1String("file"));
63 static QString bundle_string(QLatin1String("bundle"));
65 class QQmlFilePrivate;
66 class QQmlFileNetworkReply : public QObject
70 QQmlFileNetworkReply(QQmlEngine *, QQmlFilePrivate *, const QUrl &);
71 ~QQmlFileNetworkReply();
75 void downloadProgress(qint64, qint64);
78 void networkFinished();
79 void networkDownloadProgress(qint64, qint64);
82 static int finishedIndex;
83 static int downloadProgressIndex;
84 static int networkFinishedIndex;
85 static int networkDownloadProgressIndex;
86 static int replyFinishedIndex;
87 static int replyDownloadProgressIndex;
94 QNetworkReply *m_reply;
103 mutable QString urlString;
105 QQmlBundleData *bundle;
106 const QQmlBundle::FileEntry *file;
111 None, NotFound, CaseMismatch, Network
117 QQmlFileNetworkReply *reply;
120 int QQmlFileNetworkReply::finishedIndex = -1;
121 int QQmlFileNetworkReply::downloadProgressIndex = -1;
122 int QQmlFileNetworkReply::networkFinishedIndex = -1;
123 int QQmlFileNetworkReply::networkDownloadProgressIndex = -1;
124 int QQmlFileNetworkReply::replyFinishedIndex = -1;
125 int QQmlFileNetworkReply::replyDownloadProgressIndex = -1;
127 QQmlFileNetworkReply::QQmlFileNetworkReply(QQmlEngine *e, QQmlFilePrivate *p, const QUrl &url)
128 : m_engine(e), m_p(p), m_redirectCount(0), m_reply(0)
130 if (finishedIndex == -1) {
131 finishedIndex = QMetaMethod::fromSignal(&QQmlFileNetworkReply::finished).methodIndex();
132 downloadProgressIndex = QMetaMethod::fromSignal(&QQmlFileNetworkReply::downloadProgress).methodIndex();
133 const QMetaObject *smo = &staticMetaObject;
134 networkFinishedIndex = smo->indexOfMethod("networkFinished()");
135 networkDownloadProgressIndex = smo->indexOfMethod("networkDownloadProgress(qint64,qint64)");
137 replyFinishedIndex = QMetaMethod::fromSignal(&QNetworkReply::finished).methodIndex();
138 replyDownloadProgressIndex = QMetaMethod::fromSignal(&QNetworkReply::downloadProgress).methodIndex();
140 Q_ASSERT(finishedIndex != -1 && downloadProgressIndex != -1 &&
141 networkFinishedIndex != -1 && networkDownloadProgressIndex != -1 &&
142 replyFinishedIndex != -1 && replyDownloadProgressIndex != -1);
144 QNetworkRequest req(url);
145 req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
147 m_reply = m_engine->networkAccessManager()->get(req);
148 QMetaObject::connect(m_reply, replyFinishedIndex, this, networkFinishedIndex);
149 QMetaObject::connect(m_reply, replyDownloadProgressIndex, this, networkDownloadProgressIndex);
152 QQmlFileNetworkReply::~QQmlFileNetworkReply()
155 m_reply->disconnect();
156 m_reply->deleteLater();
160 void QQmlFileNetworkReply::networkFinished()
163 if (m_redirectCount < QQMLFILE_MAX_REDIRECT_RECURSION) {
164 QVariant redirect = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
165 if (redirect.isValid()) {
166 QUrl url = m_reply->url().resolved(redirect.toUrl());
168 QNetworkRequest req(url);
169 req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
171 m_reply->deleteLater();
172 m_reply = m_engine->networkAccessManager()->get(req);
174 QMetaObject::connect(m_reply, replyFinishedIndex,
175 this, networkFinishedIndex);
176 QMetaObject::connect(m_reply, replyDownloadProgressIndex,
177 this, networkDownloadProgressIndex);
183 if (m_reply->error()) {
184 m_p->errorString = m_reply->errorString();
185 m_p->error = QQmlFilePrivate::Network;
187 m_p->data = m_reply->readAll();
190 m_reply->deleteLater();
198 void QQmlFileNetworkReply::networkDownloadProgress(qint64 a, qint64 b)
200 emit downloadProgress(a, b);
203 QQmlFilePrivate::QQmlFilePrivate()
204 : bundle(0), file(0), error(None), reply(0)
209 : d(new QQmlFilePrivate)
213 QQmlFile::QQmlFile(QQmlEngine *e, const QUrl &url)
214 : d(new QQmlFilePrivate)
219 QQmlFile::QQmlFile(QQmlEngine *e, const QString &url)
220 : d(new QQmlFilePrivate)
225 QQmlFile::~QQmlFile()
230 d->bundle->release();
236 bool QQmlFile::isNull() const
238 return status() == Null;
241 bool QQmlFile::isReady() const
243 return status() == Ready;
246 bool QQmlFile::isError() const
248 return status() == Error;
251 bool QQmlFile::isLoading() const
253 return status() == Loading;
256 QUrl QQmlFile::url() const
258 if (!d->urlString.isEmpty()) {
259 d->url = QUrl(d->urlString);
260 d->urlString = QString();
265 QQmlFile::Status QQmlFile::status() const
267 if (d->url.isEmpty() && d->urlString.isEmpty())
271 else if (d->error != QQmlFilePrivate::None)
277 QString QQmlFile::error() const
281 case QQmlFilePrivate::None:
283 case QQmlFilePrivate::NotFound:
284 return QLatin1String("File not found");
285 case QQmlFilePrivate::CaseMismatch:
286 return QLatin1String("File name case mismatch");
290 qint64 QQmlFile::size() const
292 if (d->file) return d->file->fileSize();
293 else return d->data.size();
296 const char *QQmlFile::data() const
298 if (d->file) return d->file->contents();
299 else return d->data.constData();
302 QByteArray QQmlFile::dataByteArray() const
304 if (d->file) return QByteArray(d->file->contents(), d->file->fileSize());
308 QByteArray QQmlFile::metaData(const QString &name) const
312 const QQmlBundle::FileEntry *meta = d->bundle->link(d->file, name);
314 return QByteArray::fromRawData(meta->contents(), meta->fileSize());
319 void QQmlFile::load(QQmlEngine *engine, const QUrl &url)
323 QString scheme = url.scheme();
330 QQmlEnginePrivate *p = QQmlEnginePrivate::get(engine);
331 QQmlBundleData *bundle = p->typeLoader.getBundle(url.host());
333 d->error = QQmlFilePrivate::NotFound;
336 QString filename = url.path().mid(1);
337 const QQmlBundle::FileEntry *entry = bundle->find(filename);
342 d->error = QQmlFilePrivate::None;
346 } else if (isLocalFile(url)) {
347 QString lf = urlToLocalFileOrQrc(url);
349 if (!QQml_isFileCaseCorrect(lf)) {
350 d->error = QQmlFilePrivate::CaseMismatch;
355 if (file.open(QFile::ReadOnly)) {
356 d->data = file.readAll();
358 d->error = QQmlFilePrivate::NotFound;
361 d->reply = new QQmlFileNetworkReply(engine, d, url);
365 void QQmlFile::load(QQmlEngine *engine, const QString &url)
375 QQmlEnginePrivate *p = QQmlEnginePrivate::get(engine);
377 d->error = QQmlFilePrivate::NotFound;
379 int index = url.indexOf(QLatin1Char('/'), 9);
383 QStringRef identifier(&url, 9, index - 9);
385 QQmlBundleData *bundle = p->typeLoader.getBundle(identifier);
387 d->error = QQmlFilePrivate::NotFound;
390 QString filename = url.mid(index);
391 const QQmlBundle::FileEntry *entry = bundle->find(filename);
393 d->data = QByteArray(entry->contents(), entry->fileSize());
394 d->error = QQmlFilePrivate::None;
399 } else if (isLocalFile(url)) {
400 QString lf = urlToLocalFileOrQrc(url);
402 if (!QQml_isFileCaseCorrect(lf)) {
403 d->error = QQmlFilePrivate::CaseMismatch;
408 if (file.open(QFile::ReadOnly)) {
409 d->data = file.readAll();
411 d->error = QQmlFilePrivate::NotFound;
416 d->urlString = QString();
417 d->reply = new QQmlFileNetworkReply(engine, d, qurl);
421 void QQmlFile::clear()
424 d->urlString = QString();
425 d->data = QByteArray();
426 if (d->bundle) d->bundle->release();
429 d->error = QQmlFilePrivate::None;
432 void QQmlFile::clear(QObject *)
437 bool QQmlFile::connectFinished(QObject *object, const char *method)
439 if (!d || !d->reply) {
440 qWarning("QQmlFile: connectFinished() called when not loading.");
444 return QObject::connect(d->reply, SIGNAL(finished()),
448 bool QQmlFile::connectFinished(QObject *object, int method)
450 if (!d || !d->reply) {
451 qWarning("QQmlFile: connectFinished() called when not loading.");
455 return QMetaObject::connect(d->reply, QQmlFileNetworkReply::finishedIndex,
459 bool QQmlFile::connectDownloadProgress(QObject *object, const char *method)
461 if (!d || !d->reply) {
462 qWarning("QQmlFile: connectDownloadProgress() called when not loading.");
466 return QObject::connect(d->reply, SIGNAL(downloadProgress(qint64,qint64)),
470 bool QQmlFile::connectDownloadProgress(QObject *object, int method)
472 if (!d || !d->reply) {
473 qWarning("QQmlFile: connectDownloadProgress() called when not loading.");
477 return QMetaObject::connect(d->reply, QQmlFileNetworkReply::downloadProgressIndex,
482 Returns true if QQmlFile will open \a url synchronously.
484 Synchronous urls have a qrc:/, file://, or bundle:// scheme.
486 bool QQmlFile::isSynchronous(const QUrl &url)
488 QString scheme = url.scheme();
490 if ((scheme.length() == 4 && 0 == scheme.compare(file_string, Qt::CaseInsensitive)) ||
491 (scheme.length() == 6 && 0 == scheme.compare(bundle_string, Qt::CaseInsensitive)) ||
492 (scheme.length() == 3 && 0 == scheme.compare(qrc_string, Qt::CaseInsensitive)))
499 Returns true if QQmlFile will open \a url synchronously.
501 Synchronous urls have a qrc:/, file://, or bundle:// scheme.
503 bool QQmlFile::isSynchronous(const QString &url)
505 if (url.length() < 5 /* qrc:/ */)
510 if (f == QLatin1Char('f') || f == QLatin1Char('F')) {
512 return url.length() >= 7 /* file:// */ &&
513 url.startsWith(file_string, Qt::CaseInsensitive) &&
514 url[4] == QLatin1Char(':') && url[5] == QLatin1Char('/') && url[6] == QLatin1Char('/');
516 } else if (f == QLatin1Char('b') || f == QLatin1Char('B')) {
518 return url.length() >= 9 /* bundle:// */ &&
519 url.startsWith(bundle_string, Qt::CaseInsensitive) &&
520 url[6] == QLatin1Char(':') && url[7] == QLatin1Char('/') && url[8] == QLatin1Char('/');
522 } else if (f == QLatin1Char('q') || f == QLatin1Char('Q')) {
524 return url.length() >= 5 /* qrc:/ */ &&
525 url.startsWith(qrc_string, Qt::CaseInsensitive) &&
526 url[3] == QLatin1Char(':') && url[4] == QLatin1Char('/');
534 Returns true if \a url is a bundle.
536 Bundle urls have a bundle:// scheme.
538 bool QQmlFile::isBundle(const QString &url)
540 return url.length() >= 9 && url.startsWith(bundle_string, Qt::CaseInsensitive) &&
541 url[6] == QLatin1Char(':') && url[7] == QLatin1Char('/') && url[8] == QLatin1Char('/');
545 Returns true if \a url is a bundle.
547 Bundle urls have a bundle:// scheme.
549 bool QQmlFile::isBundle(const QUrl &url)
551 QString scheme = url.scheme();
553 return scheme.length() == 6 && 0 == scheme.compare(bundle_string, Qt::CaseInsensitive);
557 Returns true if \a url is a local file that can be opened with QFile.
559 Local file urls have either a qrc:/ or file:// scheme.
561 bool QQmlFile::isLocalFile(const QUrl &url)
563 QString scheme = url.scheme();
565 if ((scheme.length() == 4 && 0 == scheme.compare(file_string, Qt::CaseInsensitive)) ||
566 (scheme.length() == 3 && 0 == scheme.compare(qrc_string, Qt::CaseInsensitive)))
573 Returns true if \a url is a local file that can be opened with QFile.
575 Local file urls have either a qrc:/ or file:// scheme.
577 bool QQmlFile::isLocalFile(const QString &url)
579 if (url.length() < 5 /* qrc:/ */)
584 if (f == QLatin1Char('f') || f == QLatin1Char('F')) {
586 return url.length() >= 7 /* file:// */ &&
587 url.startsWith(file_string, Qt::CaseInsensitive) &&
588 url[4] == QLatin1Char(':') && url[5] == QLatin1Char('/') && url[6] == QLatin1Char('/');
590 } else if (f == QLatin1Char('q') || f == QLatin1Char('Q')) {
592 return url.length() >= 5 /* qrc:/ */ &&
593 url.startsWith(qrc_string, Qt::CaseInsensitive) &&
594 url[3] == QLatin1Char(':') && url[4] == QLatin1Char('/');
602 If \a url is a local file returns a path suitable for passing to QFile. Otherwise returns an
605 QString QQmlFile::urlToLocalFileOrQrc(const QUrl& url)
607 if (url.scheme().compare(QLatin1String("qrc"), Qt::CaseInsensitive) == 0) {
608 if (url.authority().isEmpty())
609 return QLatin1Char(':') + url.path();
612 return url.toLocalFile();
615 static QString toLocalFile(const QString &url)
617 if (!url.startsWith(QLatin1String("file://"), Qt::CaseInsensitive))
620 QString file = url.mid(7);
622 //XXX TODO: handle windows hostnames: "//servername/path/to/file.txt"
624 // magic for drives on windows
625 if (file.length() > 2 && file.at(0) == QLatin1Char('/') && file.at(2) == QLatin1Char(':'))
632 If \a url is a local file returns a path suitable for passing to QFile. Otherwise returns an
635 QString QQmlFile::urlToLocalFileOrQrc(const QString& url)
637 if (url.startsWith(QLatin1String("qrc:"), Qt::CaseInsensitive)) {
638 if (url.length() > 4)
639 return QLatin1Char(':') + url.mid(4);
643 return toLocalFile(url);
646 bool QQmlFile::bundleDirectoryExists(const QString &dir, QQmlEngine *e)
651 int index = dir.indexOf(QLatin1Char('/'), 9);
653 if (index == -1 && dir.length() > 9) // We accept "bundle://<blah>" with no extra path
654 index = dir.length();
659 QStringRef identifier(&dir, 9, index - 9);
660 QStringRef path(&dir, index + 1, dir.length() - index - 1);
662 QQmlBundleData *bundle = QQmlEnginePrivate::get(e)->typeLoader.getBundle(identifier);
665 int lastIndex = dir.lastIndexOf(QLatin1Char('/'));
667 if (lastIndex <= index) {
672 QStringRef d(&dir, index + 1, lastIndex - index);
674 QList<const QQmlBundle::FileEntry *> entries = bundle->files();
676 for (int ii = 0; ii < entries.count(); ++ii) {
677 QString name = entries.at(ii)->fileName();
678 if (name.startsWith(d)) {
690 bool QQmlFile::bundleDirectoryExists(const QUrl &url, QQmlEngine *e)
695 QQmlBundleData *bundle = QQmlEnginePrivate::get(e)->typeLoader.getBundle(url.host());
698 QString path = url.path();
700 int lastIndex = path.lastIndexOf(QLatin1Char('/'));
702 if (lastIndex == -1) {
707 QStringRef d(&path, 0, lastIndex);
709 QList<const QQmlBundle::FileEntry *> entries = bundle->files();
711 for (int ii = 0; ii < entries.count(); ++ii) {
712 QString name = entries.at(ii)->fileName();
713 if (name.startsWith(d)) {
725 bool QQmlFile::bundleFileExists(const QString &file, QQmlEngine *e)
730 int index = file.indexOf(QLatin1Char('/'), 9);
735 QStringRef identifier(&file, 9, index - 9);
736 QStringRef path(&file, index + 1, file.length() - index - 1);
738 QQmlBundleData *bundle = QQmlEnginePrivate::get(e)->typeLoader.getBundle(identifier);
741 const QQmlBundle::FileEntry *entry = bundle->find(path.constData(), path.length());
750 bool QQmlFile::bundleFileExists(const QUrl &, QQmlEngine *)
752 qFatal("Not implemented");
757 Returns the file name for the bundle file referenced by \a url or an
758 empty string if \a url isn't a bundle url.
760 QString QQmlFile::bundleFileName(const QString &url, QQmlEngine *e)
765 int index = url.indexOf(QLatin1Char('/'), 9);
768 index = url.length();
770 QStringRef identifier(&url, 9, index - 9);
772 QQmlBundleData *bundle = QQmlEnginePrivate::get(e)->typeLoader.getBundle(identifier);
775 QString rv = bundle->fileName;
784 Returns the file name for the bundle file referenced by \a url or an
785 empty string if \a url isn't a bundle url.
787 QString QQmlFile::bundleFileName(const QUrl &url, QQmlEngine *e)
792 QQmlBundleData *bundle = QQmlEnginePrivate::get(e)->typeLoader.getBundle(url.host());
795 QString rv = bundle->fileName;
805 #include "qqmlfile.moc"