1/****************************************************************************
2**
3** Copyright (C) 2019 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQml module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include <private/qqmldatablob_p.h>
41#include <private/qqmlglobal_p.h>
42#include <private/qqmlprofiler_p.h>
43#include <private/qqmltypeloader_p.h>
44#include <private/qqmltypeloaderthread_p.h>
45
46#include <QtQml/qqmlengine.h>
47
48#ifdef DATABLOB_DEBUG
49#define ASSERT_CALLBACK() do { if (!m_typeLoader || !m_typeLoader->m_thread->isThisThread()) qFatal("QQmlDataBlob: An API call was made outside a callback"); } while (false)
50#else
51#define ASSERT_CALLBACK()
52#endif
53
54DEFINE_BOOL_CONFIG_OPTION(dumpErrors, QML_DUMP_ERRORS);
55
56QT_BEGIN_NAMESPACE
57
58/*!
59\class QQmlDataBlob
60\brief The QQmlDataBlob encapsulates a data request that can be issued to a QQmlTypeLoader.
61\internal
62
63QQmlDataBlob's are loaded by a QQmlTypeLoader. The user creates the QQmlDataBlob
64and then calls QQmlTypeLoader::load() or QQmlTypeLoader::loadWithStaticData() to load it.
65The QQmlTypeLoader invokes callbacks on the QQmlDataBlob as data becomes available.
66*/
67
68/*!
69\enum QQmlDataBlob::Status
70
71This enum describes the status of the data blob.
72
73\list
74\li Null The blob has not yet been loaded by a QQmlTypeLoader
75\li Loading The blob is loading network data. The QQmlDataBlob::setData() callback has not yet been
76 invoked or has not yet returned.
77\li WaitingForDependencies The blob is waiting for dependencies to be done before continuing.
78 This status only occurs after the QQmlDataBlob::setData() callback has been made, and when the
79 blob has outstanding dependencies.
80\li Complete The blob's data has been loaded and all dependencies are done.
81\li Error An error has been set on this blob.
82\endlist
83*/
84
85/*!
86\enum QQmlDataBlob::Type
87
88This enum describes the type of the data blob.
89
90\list
91\li QmlFile This is a QQmlTypeData
92\li JavaScriptFile This is a QQmlScriptData
93\li QmldirFile This is a QQmlQmldirData
94\endlist
95*/
96
97/*!
98Create a new QQmlDataBlob for \a url and of the provided \a type.
99*/
100QQmlDataBlob::QQmlDataBlob(const QUrl &url, Type type, QQmlTypeLoader *manager)
101: m_typeLoader(manager), m_type(type), m_url(url), m_finalUrl(url), m_redirectCount(0),
102 m_inCallback(false), m_isDone(false)
103{
104 //Set here because we need to get the engine from the manager
105 if (m_typeLoader->engine() && m_typeLoader->engine()->urlInterceptor())
106 m_url = m_typeLoader->engine()->urlInterceptor()->intercept(m_url,
107 (QQmlAbstractUrlInterceptor::DataType)m_type);
108}
109
110/*! \internal */
111QQmlDataBlob::~QQmlDataBlob()
112{
113 Q_ASSERT(m_waitingOnMe.isEmpty());
114
115 cancelAllWaitingFor();
116}
117
118/*!
119 Must be called before loading can occur.
120*/
121void QQmlDataBlob::startLoading()
122{
123 Q_ASSERT(status() == QQmlDataBlob::Null);
124 m_data.setStatus(QQmlDataBlob::Loading);
125}
126
127/*!
128Returns the type provided to the constructor.
129*/
130QQmlDataBlob::Type QQmlDataBlob::type() const
131{
132 return m_type;
133}
134
135/*!
136Returns the blob's status.
137*/
138QQmlDataBlob::Status QQmlDataBlob::status() const
139{
140 return m_data.status();
141}
142
143/*!
144Returns true if the status is Null.
145*/
146bool QQmlDataBlob::isNull() const
147{
148 return status() == Null;
149}
150
151/*!
152Returns true if the status is Loading.
153*/
154bool QQmlDataBlob::isLoading() const
155{
156 return status() == Loading;
157}
158
159/*!
160Returns true if the status is WaitingForDependencies.
161*/
162bool QQmlDataBlob::isWaiting() const
163{
164 return status() == WaitingForDependencies ||
165 status() == ResolvingDependencies;
166}
167
168/*!
169Returns true if the status is Complete.
170*/
171bool QQmlDataBlob::isComplete() const
172{
173 return status() == Complete;
174}
175
176/*!
177Returns true if the status is Error.
178*/
179bool QQmlDataBlob::isError() const
180{
181 return status() == Error;
182}
183
184/*!
185Returns true if the status is Complete or Error.
186*/
187bool QQmlDataBlob::isCompleteOrError() const
188{
189 Status s = status();
190 return s == Error || s == Complete;
191}
192
193/*!
194Returns the data download progress from 0 to 1.
195*/
196qreal QQmlDataBlob::progress() const
197{
198 quint8 p = m_data.progress();
199 if (p == 0xFF) return 1.;
200 else return qreal(p) / qreal(0xFF);
201}
202
203/*!
204Returns the physical url of the data. Initially this is the same as
205finalUrl(), but if a URL interceptor is set, it will work on this URL
206and leave finalUrl() alone.
207
208\sa finalUrl()
209*/
210QUrl QQmlDataBlob::url() const
211{
212 return m_url;
213}
214
215QString QQmlDataBlob::urlString() const
216{
217 if (m_urlString.isEmpty())
218 m_urlString = m_url.toString();
219
220 return m_urlString;
221}
222
223/*!
224Returns the logical URL to be used for resolving further URLs referred to in
225the code.
226
227This is the blob url passed to the constructor. If a URL interceptor rewrites
228the URL, this one stays the same. If a network redirect happens while fetching
229the data, this url is updated to reflect the new location. Therefore, if both
230an interception and a redirection happen, the final url will indirectly
231incorporate the result of the interception, potentially breaking further
232lookups.
233
234\sa url()
235*/
236QUrl QQmlDataBlob::finalUrl() const
237{
238 return m_finalUrl;
239}
240
241/*!
242Returns the finalUrl() as a string.
243*/
244QString QQmlDataBlob::finalUrlString() const
245{
246 if (m_finalUrlString.isEmpty())
247 m_finalUrlString = m_finalUrl.toString();
248
249 return m_finalUrlString;
250}
251
252/*!
253Return the errors on this blob.
254
255May only be called from the load thread, or after the blob isCompleteOrError().
256*/
257QList<QQmlError> QQmlDataBlob::errors() const
258{
259 Q_ASSERT(isCompleteOrError() || (m_typeLoader && m_typeLoader->m_thread->isThisThread()));
260 return m_errors;
261}
262
263/*!
264Mark this blob as having \a errors.
265
266All outstanding dependencies will be cancelled. Requests to add new dependencies
267will be ignored. Entry into the Error state is irreversable.
268
269The setError() method may only be called from within a QQmlDataBlob callback.
270*/
271void QQmlDataBlob::setError(const QQmlError &errors)
272{
273 ASSERT_CALLBACK();
274
275 QList<QQmlError> l;
276 l << errors;
277 setError(l);
278}
279
280/*!
281\overload
282*/
283void QQmlDataBlob::setError(const QList<QQmlError> &errors)
284{
285 ASSERT_CALLBACK();
286
287 Q_ASSERT(status() != Error);
288 Q_ASSERT(m_errors.isEmpty());
289
290 m_errors = errors; // Must be set before the m_data fence
291 m_data.setStatus(Error);
292
293 if (dumpErrors()) {
294 qWarning().nospace() << "Errors for " << urlString();
295 for (int ii = 0; ii < errors.count(); ++ii)
296 qWarning().nospace() << " " << qPrintable(errors.at(ii).toString());
297 }
298 cancelAllWaitingFor();
299
300 if (!m_inCallback)
301 tryDone();
302}
303
304void QQmlDataBlob::setError(const QQmlJS::DiagnosticMessage &error)
305{
306 QQmlError e;
307 e.setColumn(error.column);
308 e.setLine(error.line);
309 e.setDescription(error.message);
310 e.setUrl(url());
311 setError(e);
312}
313
314void QQmlDataBlob::setError(const QVector<QQmlJS::DiagnosticMessage> &errors)
315{
316 QList<QQmlError> finalErrors;
317 finalErrors.reserve(errors.count());
318 for (const auto &error : errors) {
319 QQmlError e;
320 e.setColumn(error.column);
321 e.setLine(error.line);
322 e.setDescription(error.message);
323 e.setUrl(url());
324 finalErrors << e;
325 }
326 setError(finalErrors);
327}
328
329void QQmlDataBlob::setError(const QString &description)
330{
331 QQmlError e;
332 e.setDescription(description);
333 e.setUrl(url());
334 setError(e);
335}
336
337/*!
338Wait for \a blob to become complete or to error. If \a blob is already
339complete or in error, or this blob is already complete, this has no effect.
340
341The setError() method may only be called from within a QQmlDataBlob callback.
342*/
343void QQmlDataBlob::addDependency(QQmlDataBlob *blob)
344{
345 ASSERT_CALLBACK();
346
347 Q_ASSERT(status() != Null);
348
349 if (!blob ||
350 blob->status() == Error || blob->status() == Complete ||
351 status() == Error || status() == Complete || m_isDone)
352 return;
353
354 for (auto existingDep: qAsConst(m_waitingFor))
355 if (existingDep.data() == blob)
356 return;
357
358 m_data.setStatus(WaitingForDependencies);
359
360 m_waitingFor.append(blob);
361 blob->m_waitingOnMe.append(this);
362}
363
364/*!
365\fn void QQmlDataBlob::dataReceived(const Data &data)
366
367Invoked when data for the blob is received. Implementors should use this callback
368to determine a blob's dependencies. Within this callback you may call setError()
369or addDependency().
370*/
371
372/*!
373Invoked once data has either been received or a network error occurred, and all
374dependencies are complete.
375
376You can set an error in this method, but you cannot add new dependencies. Implementors
377should use this callback to finalize processing of data.
378
379The default implementation does nothing.
380
381XXX Rename processData() or some such to avoid confusion between done() (processing thread)
382and completed() (main thread)
383*/
384void QQmlDataBlob::done()
385{
386}
387
388#if QT_CONFIG(qml_network)
389/*!
390Invoked if there is a network error while fetching this blob.
391
392The default implementation sets an appropriate QQmlError.
393*/
394void QQmlDataBlob::networkError(QNetworkReply::NetworkError networkError)
395{
396 Q_UNUSED(networkError);
397
398 QQmlError error;
399 error.setUrl(m_url);
400
401 const char *errorString = nullptr;
402 switch (networkError) {
403 default:
404 errorString = "Network error";
405 break;
406 case QNetworkReply::ConnectionRefusedError:
407 errorString = "Connection refused";
408 break;
409 case QNetworkReply::RemoteHostClosedError:
410 errorString = "Remote host closed the connection";
411 break;
412 case QNetworkReply::HostNotFoundError:
413 errorString = "Host not found";
414 break;
415 case QNetworkReply::TimeoutError:
416 errorString = "Timeout";
417 break;
418 case QNetworkReply::ProxyConnectionRefusedError:
419 case QNetworkReply::ProxyConnectionClosedError:
420 case QNetworkReply::ProxyNotFoundError:
421 case QNetworkReply::ProxyTimeoutError:
422 case QNetworkReply::ProxyAuthenticationRequiredError:
423 case QNetworkReply::UnknownProxyError:
424 errorString = "Proxy error";
425 break;
426 case QNetworkReply::ContentAccessDenied:
427 errorString = "Access denied";
428 break;
429 case QNetworkReply::ContentNotFoundError:
430 errorString = "File not found";
431 break;
432 case QNetworkReply::AuthenticationRequiredError:
433 errorString = "Authentication required";
434 break;
435 };
436
437 error.setDescription(QLatin1String(errorString));
438
439 setError(error);
440}
441#endif // qml_network
442
443/*!
444Called if \a blob, which was previously waited for, has an error.
445
446The default implementation does nothing.
447*/
448void QQmlDataBlob::dependencyError(QQmlDataBlob *blob)
449{
450 Q_UNUSED(blob);
451}
452
453/*!
454Called if \a blob, which was previously waited for, has completed.
455
456The default implementation does nothing.
457*/
458void QQmlDataBlob::dependencyComplete(QQmlDataBlob *blob)
459{
460 Q_UNUSED(blob);
461}
462
463/*!
464Called when all blobs waited for have completed. This occurs regardless of
465whether they are in error, or complete state.
466
467The default implementation does nothing.
468*/
469void QQmlDataBlob::allDependenciesDone()
470{
471 m_data.setStatus(QQmlDataBlob::ResolvingDependencies);
472}
473
474/*!
475Called when the download progress of this blob changes. \a progress goes
476from 0 to 1.
477
478This callback is only invoked if an asynchronous load for this blob is
479made. An asynchronous load is one in which the Asynchronous mode is
480specified explicitly, or one that is implicitly delayed due to a network
481operation.
482
483The default implementation does nothing.
484*/
485void QQmlDataBlob::downloadProgressChanged(qreal progress)
486{
487 Q_UNUSED(progress);
488}
489
490/*!
491Invoked on the main thread sometime after done() was called on the load thread.
492
493You cannot modify the blobs state at all in this callback and cannot depend on the
494order or timeliness of these callbacks. Implementors should use this callback to notify
495dependencies on the main thread that the blob is done and not a lot else.
496
497This callback is only invoked if an asynchronous load for this blob is
498made. An asynchronous load is one in which the Asynchronous mode is
499specified explicitly, or one that is implicitly delayed due to a network
500operation.
501
502The default implementation does nothing.
503*/
504void QQmlDataBlob::completed()
505{
506}
507
508
509void QQmlDataBlob::tryDone()
510{
511 if (status() != Loading && m_waitingFor.isEmpty() && !m_isDone) {
512 m_isDone = true;
513 addref();
514
515#ifdef DATABLOB_DEBUG
516 qWarning("QQmlDataBlob::done() %s", qPrintable(urlString()));
517#endif
518 done();
519
520 if (status() != Error)
521 m_data.setStatus(Complete);
522
523 notifyAllWaitingOnMe();
524
525 // Locking is not required here, as anyone expecting callbacks must
526 // already be protected against the blob being completed (as set above);
527#ifdef DATABLOB_DEBUG
528 qWarning("QQmlDataBlob: Dispatching completed");
529#endif
530 m_typeLoader->m_thread->callCompleted(this);
531
532 release();
533 }
534}
535
536void QQmlDataBlob::cancelAllWaitingFor()
537{
538 while (m_waitingFor.count()) {
539 QQmlRefPointer<QQmlDataBlob> blob = m_waitingFor.takeLast();
540
541 Q_ASSERT(blob->m_waitingOnMe.contains(this));
542
543 blob->m_waitingOnMe.removeOne(this);
544 }
545}
546
547void QQmlDataBlob::notifyAllWaitingOnMe()
548{
549 while (m_waitingOnMe.count()) {
550 QQmlDataBlob *blob = m_waitingOnMe.takeLast();
551
552 Q_ASSERT(std::any_of(blob->m_waitingFor.constBegin(), blob->m_waitingFor.constEnd(),
553 [this](const QQmlRefPointer<QQmlDataBlob> &waiting) { return waiting.data() == this; }));
554
555 blob->notifyComplete(this);
556 }
557}
558
559void QQmlDataBlob::notifyComplete(QQmlDataBlob *blob)
560{
561 Q_ASSERT(blob->status() == Error || blob->status() == Complete);
562 QQmlCompilingProfiler prof(typeLoader()->profiler(), blob);
563
564 m_inCallback = true;
565
566 QQmlRefPointer<QQmlDataBlob> blobRef;
567 for (int i = 0; i < m_waitingFor.count(); ++i) {
568 if (m_waitingFor.at(i).data() == blob) {
569 blobRef = m_waitingFor.takeAt(i);
570 break;
571 }
572 }
573 Q_ASSERT(blobRef);
574
575 if (blob->status() == Error) {
576 dependencyError(blob);
577 } else if (blob->status() == Complete) {
578 dependencyComplete(blob);
579 }
580
581 if (!isError() && m_waitingFor.isEmpty())
582 allDependenciesDone();
583
584 m_inCallback = false;
585
586 tryDone();
587}
588
589QString QQmlDataBlob::SourceCodeData::readAll(QString *error) const
590{
591 error->clear();
592 if (hasInlineSourceCode)
593 return inlineSourceCode;
594
595 QFile f(fileInfo.absoluteFilePath());
596 if (!f.open(QIODevice::ReadOnly)) {
597 *error = f.errorString();
598 return QString();
599 }
600
601 const qint64 fileSize = fileInfo.size();
602
603 if (uchar *mappedData = f.map(0, fileSize)) {
604 QString source = QString::fromUtf8(reinterpret_cast<const char *>(mappedData), fileSize);
605 f.unmap(mappedData);
606 return source;
607 }
608
609 QByteArray data(fileSize, Qt::Uninitialized);
610 if (f.read(data.data(), data.length()) != data.length()) {
611 *error = f.errorString();
612 return QString();
613 }
614 return QString::fromUtf8(data);
615}
616
617QDateTime QQmlDataBlob::SourceCodeData::sourceTimeStamp() const
618{
619 if (hasInlineSourceCode)
620 return QDateTime();
621
622 return fileInfo.lastModified();
623}
624
625bool QQmlDataBlob::SourceCodeData::exists() const
626{
627 if (hasInlineSourceCode)
628 return true;
629 return fileInfo.exists();
630}
631
632bool QQmlDataBlob::SourceCodeData::isEmpty() const
633{
634 if (hasInlineSourceCode)
635 return inlineSourceCode.isEmpty();
636 return fileInfo.size() == 0;
637}
638
639QT_END_NAMESPACE
640